Anzeige
Tutorialbeschreibung

Javascript und Ajax - Formulare

Javascript und Ajax - Formulare

Das nun folgende Tutorial ist ein Auszug aus der 7. Auflage des Buches: JavaScript und Ajax von Christian Wenz.

Kapitel 9 Formulare

Es gibt prinzipiell drei Einsatzgebiete für JavaScript: grafische Effekte (Beispiele gibt es zum ersten Mal in Kapitel 11, Navigationshilfen zum Beispiel in Kapitel 8 und 31) und echte Interaktion mit dem Benutzer. Interaktion bedeutet hier, dass der Benutzer etwas tut und die Webseite sich dementsprechend verändert. Sehr oft benutzt man hierzu HTML-Formulare. Das hat mehrere Gründe. HTML-Schaltflächen eignen sich vorzüglich, um JavaScript-Funktionen auszuführen, und in HTML-Eingabefeldern kann man Daten vom Benutzer abfragen. In diesem Kapitel soll es primär darum gehen, einen HTML-Fragebogen mit JavaScript-Funktionalität zu versehen.


9.1 Überprüfung auf Vollständigkeit 
topBilder

Sie haben das garantiert schon einmal im World Wide Web gesehen: Wenn Sie sich bei einem Freemail-Dienst, einer Online-Community oder bei einem Online-Kaufhaus registrieren, müssen Sie einige persönliche Angaben machen. Die Betreiber der Website haben natürlich ein großes Interesse daran, dass das Formular vollständig ausgefüllt wird. Das kann serverseitig gelöst werden, was den Vorteil hat, dass die Überprüfung auch mit wirklich jedem Browser funktioniert. Die Nachteile liegen aber gleichzeitig auf der Hand. Nicht immer steht dem Programmierer eine serverseitige Programmiersprachen-Unterstützung zur Verfügung. Der Programmieraufwand ist hierbei recht hoch, denn es soll ja nicht nur eine Fehlermeldung ausgegeben werden, sondern wenn möglich das Formular auch noch mit den zuvor eingegebenen Daten dargestellt werden. Es wäre schon sehr ärgerlich für den Benutzer, wenn er ein Formular fast vollständig ausgefüllt hätte und aufgrund einer einzigen Auslassung das komplette Formular noch einmal ausfüllen müsste.

Mit JavaScript kann auch auf der Clientseite die Überprüfung auf Vollständigkeit durchgeführt werden. Damit wird keine neue Seite in den Browser geladen. Die Formulardaten bleiben unangetastet, und der Benutzer kann sofort den Fehler korrigieren.

Im Folgenden finden Sie ein HTML-Formular, das ausschließlich aus Pflichtfeldern bestehen soll. Nach dem Anklicken der Absenden-Schaltfläche soll die Funktion pruefen() aufgerufen werden, die überprüft, ob das Formular vollständig ausgefüllt worden ist, und gegebenenfalls eine Fehlermeldung ausgibt. Diese Funktion wird in diesem Kapitel schrittweise erarbeitet. Vorab jedoch gibt es noch ein paar Hinweise, wie diese Funktion in das HTML-Dokument eingebunden wird.

Das <form>-Tag hat den Event-Handler onsubmit, der ausgelöst wird, sobald das Formular abgeschickt wird. Wie schon bei anderen Beispielen in diesem Buch kann hier ein altbekannter Trick angewandt werden. Beim Versenden des Formulars kollidieren wieder die HTML-Konsequenzen des Versendens (nämlich das Aufrufen einer anderen Seite) und die mit onsubmit zugeordneten JavaScript-Aktionen. Endet der onsubmit-Event-Handler jedoch auf return false, so wird die HTML-Aktion (das Aufrufen der anderen Seite) nicht ausgeführt; bei return true wird sie ausgeführt. Also hat das <form>-Tag in diesem Beispiel folgenden Aufbau:

<form action="/cgi-bin/skript" name="Fragebogen"
onsubmit="return pruefen();">

Die Funktion pruefen() gibt nur dann true zurück, wenn das Formular vollständig ausgefüllt worden ist, ansonsten gibt sie false zurück. Somit ist sichergestellt, dass nur vollständige Formulare verschickt werden – sofern der Browser des Benutzers JavaScript unterstützt und der Benutzer es auch aktiviert hat. Ältere Browser oder solche mit ausgeschaltetem JavaScript ignorieren den Skriptteil komplett und versenden das Formular direkt.

<html>
<head>
<title>Formular-Überprüfung</title>
<script type="text/javascript"><!--
function pruefen() {
   // ... kommt noch
   return true;
}
//--></script>
</head>
<body>
<h1>Anmeldeformular</h1>
<form action="/cgi-bin/skript" name="Fragebogen"
onsubmit="return pruefen()">
Vorname: <input type="text" name="Vorname" /><br />
Nachname: <input type="text" name="Nachname" /><br />
E-Mail-Adresse: <input type="text" name="Email" /><br />
<input type="radio" name="Geschlecht" value="m" />
männlich |
   <input type="radio" name="Geschlecht" value="w" />
   weiblich<br />
Welche Bücher von Galileo Press besitzen Sie?<br />
   <input type="checkbox" name="Flash" /> Flash |
   <input type="checkbox" name="JavaScript"> JavaScript |
   <input type="checkbox" name="ActionScript">
   Einstieg in ActionScript<br />
Welches der Bücher hat das schönste Cover?
   <select name="Cover">
   <option>Bitte wählen</option>
   <option value="Flash">Flash</option>
   <option value="JavaScript">JavaScript</option>
   <option value="ActionScript">Einstieg in ActionScript
</option>
   </select><br />
<input type="submit" value="Absenden" />
<input type="reset" value="Formular löschen" />
</form>
</body>
</html>

Bilder

Abbildung 9.1     Das (noch) funktionslose Formular


9.1.1 Allgemeiner Aufbau  

Alle Formulare werden im Array document.forms gespeichert. Auf dieses Formular kann über den Array-Index zugegriffen werden, wobei die Formulare in der Reihenfolge im Array stehen, in der sie im HTML-Dokument vorkommen. Die Nummerierung beginnt beim Index 0. In unserem Beispiel benutzen wir also document.forms[0].

Alternativ dazu kann man auch über das name-Attribut des <form>-Tags auf das Formular zugreifen, im Beispiel also mit document.Fragebogen. Sollte das name-Attribut Spezialzeichen wie etwa das Leerzeichen enthalten (beispielsweise "Mein Fragebogen"), so kann noch mit document. forms["Mein Fragebogen"] darauf zugegriffen werden, allerdings erst ab JavaScript Version 1.1. Im Allgemeinen aber sollten Sie den Array-Index verwenden. Da die meisten HTML-Seiten maximal ein Formular enthalten, reicht ein document.forms[0], und es ist auch nicht sonderlich unleserlich. Bei anderen Arrays, die viele Elemente besitzen, ist dieses Vorgehen sicherlich unpraktikabel.

Das Formular speichert all seine Elemente (also alle HTML-Formularelemente in diesem Formular) im Array elements. Auch hier kann man auf die einzelnen Elemente über den Array-Index zugreifen; im obigen Beispiel greift man auf das erste Texteingabefeld mit document.forms[0]. elements[0] zu. Alternativ können Sie auch wieder das name-Attribut verwenden, in diesem Fall document.forms[0].Vorname. Die meisten Formularelemente haben eine Eigenschaft value, die in der Regel die Eingabe des Benutzers angibt. Details hierzu finden Sie in den entsprechenden Abschnitten.

Zur Vereinfachung wird folgende Zeile am Anfang der Funktion pruefen() eingeführt:

var f = document.Fragebogen;

Hiermit spart man sich ein wenig Tipparbeit; statt document. Fragebogen.elements[0] reicht jetzt f.elements[0].


9.1.2 Texteingabefelder  

Unter Texteingabefeldern verstehen wir in diesem Zusammenhang Formularfelder, die folgendermaßen in HTML abgebildet werden:

gpBilder
 
<input type="text" />einzeiliges Texteingabefeld
gpBilder
 
<input type="password" />einzeiliges Passwortfeld
gpBilder
 
<input type="hidden" />unsichtbares Formularfeld
gpBilder
 
<textaera>mehrzeiliges Texteingabefeld

In diesem Fall gestaltet sich die Vollständigkeitsabfrage sehr einfach. Wie oben schon ausgeführt wurde, heißt die Eigenschaft, die den Eingabewert im Texteingabefeld enthält, value. Der folgende JavaScript-Code kann also in die Funktion pruefen() eingefügt werden. Er funktioniert folgendermaßen: Ist ein Texteingabefeld nicht ausgefüllt, wird die Funktion mit return false verlassen, was dann zur Folge hat, dass das Formular nicht versandt wird.

if (f.Vorname.value == "") {
   return false;
}
if (f.Nachname.value == "") {
   return false;
}
if (f.Email.value == "") {
   return false;
}

Auf eine Besonderheit bei Texteingabefeldern muss ich noch hinweisen: Wenn Sie mehrere Texteingabefelder mit identischen name-Attributen haben, werden die Werte in einem Array gespeichert. Enthielte das Beispielformular von oben also noch ein Texteingabefeld namens Vorname, könnte man auf das erste dieser Felder mit f.Vorname[0] zugreifen und auf das zweite Feld mit f.Vorname[1]. Aus Gründen der Einfachheit (und zur Verringerung der Fehlermöglichkeiten) ein einfacher Tipp: Verwenden Sie stets eindeutige Bezeichnungen für Ihre Formularelemente.


9.1.3 Radiobuttons  

Aus einer Gruppe Radiobuttons kann immer nur einer ausgewählt werden. Eine Gruppe von Radiobuttons ist durch ein identisches name-Attribut gekennzeichnet. Leider greift man im obigen Beispiel mit f.Geschlecht auf die Gruppe von Radiobuttons mit dem name-Attribut Geschlecht zu. Die Eigenschaft value liefert hier nicht das richtige Ergebnis: Es muss auf einen individuellen Radiobutton zugegriffen werden. Das wäre natürlich über das elements-Array möglich, aber es gibt einen eleganteren Weg. Analog zu Texteingabefeldern werden gleichnamige Radiobuttons auch in einem Array abgespeichert. Mit der folgenden Schleife werden alle Radiobuttons (in diesem Falle zwei) durchlaufen, und es wird überprüft, ob der Button ausgewählt ist (boolesche Eigenschaft checked). Wenn beide nicht ausgewählt sind, wird pruefen() mit return false verlassen.

var geschlecht_ok = false;
for (var i=0; i<f.Geschlecht.length; i++) {
   if (f.Geschlecht[i].checked) {
      geschlecht_ok = true;
   }
}
if (!geschlecht_ok) {
   return false;
}

Alternativ dazu können Sie eine Variante ohne Schleife benutzen, die etwas kürzer ist:

if (!(f.Geschlecht[0].checked || f.Geschlecht[1].checked)) {
   return false;
}


9.1.4 Checkboxen  

Checkboxen können wie Radiobuttons ausgewählt werden, wobei sich die grafische Darstellung leicht unterscheidet: Checkboxen werden angekreuzt. Da Checkboxen nicht gruppiert sind, also jede Checkbox für sich ausgewählt werden kann (oder auch nicht), ist die Überprüfung, ob sie ausgefüllt wurden, nicht weiter schwer. Wieder gibt es eine boolesche Eigenschaft checked, die angibt, ob eine Checkbox angekreuzt ist oder nicht. Das Formular ist dann unvollständig ausgefüllt, wenn keine der drei Checkboxen angekreuzt worden ist.

if (!f.Flash.checked && !f.JavaScript.checked && !f.ActionScript.checked) {
   return false;
}


9.1.5 Auswahllisten  

Eine HTML-Auswahlliste wird mit dem Tag <select> eingeleitet. Es ist sinnvoll, diesem Tag ein aussagekräftiges name-Attribut zu geben. Die einzelnen Elemente werden durch das HTML-Tag <option> dargestellt. Dieses Tag hat standardmäßig kein name-Attribut; wenn man also dort Informationen unterbringen will, sollte man das value-Attribut besetzen.

Beim Einsatz von JavaScript kann auf die Auswahlliste über das name-Attribut des <select>-Tags zugegriffen werden. Die einzelnen Optionen sind im Array options abgespeichert. Jede Option hat die boolesche Eigenschaft selected, die angibt, ob die entsprechende Option gerade ausgewählt ist. Mit der Eigenschaft value kann auf das value-Attribut der Option zugegriffen werden.

Im Fragebogen-Beispiel kommt es darauf an, dass nicht die erste Option ausgewählt wird, denn die heißt "Bitte wählen". Ein erster Versuch der Abfrage lautet also folgendermaßen:

if (f.Cover.options[0].selected) {
   return false;
}

Wenn festgestellt werden soll, welche der Optionen gerade ausgewählt ist, könnte man auf die Idee kommen, das options-Array mit einer Schleife zu durchlaufen, um so den Index der ausgewählten Option herauszubekommen:

for (var i=0; i<f.Cover.options.length; i++) {
   if (f.Cover.options[i].selected) {
      var Auswahl = i;
   }
}
if (Auswahl == 0) {
   return false;
}

Diese Arbeit kann man sich jedoch sparen: Auswahllisten haben die Eigenschaft selectedIndex, die den Array-Index der ausgewählten Option angibt. Die Abfrage kann also folgendermaßen vereinfacht werden:

if (f.Cover.selectedIndex==0) {
   return false;
}

Es gibt auch Auswahllisten, bei denen mehr als eine Option ausgewählt werden kann. Diese sind in HTML mit <select multiple> abgebildet. In diesem Fall enthält selectedIndex den Index der obersten ausgewählten Option. Wenn Sie dagegen auf alle ausgewählten Listenelemente zugreifen möchten oder wenn es mehrere »leere« Listenelemente gibt (etwa Einträge mit Trennlinien oder ohne Text), müssen Sie eine for-Schleife nach obigem Muster verwenden.


9.1.6 Fehlermeldung ausgeben  

Hier wird noch einmal der komplette Code für die Vollständigkeitsüberprüfung des Formulars wiedergegeben. Es wurden Kommandos hinzugefügt, die eine Warnmeldung ausgeben, sofern das Formular unvollständig ausgefüllt worden ist.

function pruefen(){
   var f = document.forms[0];
   var fehler = "";  //enthält die Bezeichnungen
                     //der nicht ausgefüllten Felder
   // *** Überprüfung auf vollständige Ausfüllung
   if (f.Vorname.value == "") {
      fehler += "Vorname ";
   }
   if (f.Nachname.value == "") {
      fehler += "Nachname ";
   }
   if (f.Email.value == "") {
      fehler += "E-Mail ";
   }
   if (!(f.Geschlecht[0].checked || f.Geschlecht[1]
   .checked)) {
      fehler += "Geschlecht ";
   }
   if (!f.Flash.checked && !f.JavaScript.checked &&
   !f.ActionScript.checked) {
      fehler += "Lieblingsbuch ";
   }
   if (f.Cover.selectedIndex == 0) {
      fehler += "Cover ";
   }
   // *** Gegebenenfalls Fehlermeldung
   if (fehler != "") {
      var fehlertext = "Die folgenden Felder wurden
      nicht vollständig ausgefüllt:
";
      fehlertext += fehler;
      alert(fehlertext);
      return false;
   }
   return true;
}

Bilder

Abbildung 9.2     Hinweis auf nicht ausgefüllte Felder


9.1.7 Konstruktive Vorschläge 
topBilder

Anstatt bei Texteingabefeldern eine Fehlermeldung auszugeben, kann man auch die Methode window.prompt() benutzen, um den Benutzer direkt zu einer Texteingabe zu zwingen. In Kapitel 8 wurde window. prompt() schon erläutert. Man muss in diesem Beispiel noch zusätzlich beachten, dass man die value-Eigenschaft der Texteingabefelder auch auf den eingegebenen Wert setzt, damit diese Daten auch an das serverseitige Skript übergeben werden können.

Die Funktion pruefen() muss also folgendermaßen abgeändert werden:

function pruefen() {
   var f = document.forms[0];
   var fehler = "";  //enthält die Bezeichnungen
                     //der nicht ausgefüllten Felder
   // *** Überprüfung auf vollständige Ausfüllung
   while (f.Vorname.value == "") {
      var vorname = prompt("Bitte geben Sie Ihren
      Vornamen ein!");
      if (vorname) {
         f.Vorname.value = vorname;
      }
   }
   while (f.Nachname.value == "") {
      var nachname = prompt("Bitte geben Sie Ihren
      Nachnamen ein!");
      if (nachname) {
         f.Nachname.value = nachname;
      }
   }
   while (f.Email.value == "") {
      var email = prompt("Bitte geben Sie Ihre E-Mail-
      Adresse ein!");
      if (email) {
         f.Email.value = email;
      }
   }
   if (!(f.Geschlecht[0].checked || f.Geschlecht[1]
   .checked)) {
      fehler += "Geschlecht ";
   }
   if (!f.Flash.checked && !f.JavaScript.checked &&
   !f.ActionScript.checked) {
      fehler += "Lieblingsbuch ";
   }

   if (f.Cover.selectedIndex==0) {
      fehler += "Cover ";
   }
   // *** Gegebenenfalls Fehlermeldung
   if (fehler != "") {
      var fehlertext = "Die folgenden Felder wurden
      nicht vollständig ausgefüllt:
";
      fehlertext += fehler;
      alert(fehlertext);
      return false;
   }
   return true;
}

Bilder

Abbildung 9.3     Nicht ausgefüllte Texteingabefelder können direkt korrigiert werden.

Der Benutzer steckt in einer Endlosschleife fest und wird gezwungen, die Angaben zu machen, ansonsten geht es nicht weiter. So ein Abfragefenster wirkt allerdings relativ aufdringlich, und wenn ein Benutzer partout nichts angeben will, dann macht er eben falsche Angaben. Sie werden mit diesen Mitteln niemanden dazu bringen, die Wahrheit zu sagen, wenn er nichts über sich preisgeben will. Vergisst aber jemand, ein Mussfeld auszufüllen, bieten Sie ihm eine bequeme Möglichkeit an, dies nachzuholen.

Testen Sie Ihren Code sehr gründlich. Folgender Tippfehler führt dazu, dass der Benutzer in einer Endlosschleife festsitzt, die er nicht mehr verlassen kann:

while (f.Email.value=="") {
   var email = prompt("Bitte geben Sie Ihre E-Mail-
   Adresse ein!")
   if (email) {
      f.Nachname.value = email;
   }
}

Dieser Fehler schleicht sich bei der Verwendung von Copy&Paste sehr schnell ein (raten Sie, woher ich das weiß L), und er ruiniert das Skript. Nicht das Email-Feld wird aktualisiert, sondern das Nachname-Feld. Außerdem bleibt das Email-Feld immer leer, egal was der Benutzer in das Abfragefenster eingibt. Es geht also an dieser Stelle nicht weiter, und der Benutzer kommt so schnell nicht wieder auf Ihre Seiten.

 

9.2 Automatische Überprüfung 
topBilder

Das Skript zur Überprüfung auf Vollständigkeit muss für jedes Formular neu geschrieben werden. Zwar können Sie Elemente wiederverwenden, aber etwas zusätzliche Arbeit ist immer nötig. Es ist aber möglich, die Überprüfung auch zu automatisieren. Jedes Formularelement besitzt nämlich die Eigenschaft type, die den Typ des Elements – beispielsweise Texteingabefeld, Radiobutton oder Checkbox – angibt. Man kann also den Ansatz verfolgen, in einer Schleife alle Formularelemente durchzugehen und zu überprüfen, ob sie ausgefüllt sind. Die Fehlermeldungen sind dann vielleicht nicht mehr so individuell wie im vorherigen Beispiel, aber Sie ersparen sich eine Menge Tipp- und Denkarbeit.

Im Folgenden sehen Sie das Grundgerüst für die »automatische« Version der Funktion pruefen().

<script type="text/javascript"><!--
function pruefen(f) {  //Formular wird als Parameter übergeben
   var fehler = "";  //Variable für die Fehlermeldung
   var i;  //Enthält das jeweilige Formularelement
   for (var j=0; j<f.elements.length; j++) {
      i = f.elements[j];
      //kommt noch...
   }
   return true;
}
//--></script>


9.2.1 Texteingabefelder  

Der Wert der type-Eigenschaft eines Texteingabefelds ist bei einem einzeiligen Texteingabefeld "text". Er ist "password" bei einem Passwortfeld sowie "textarea" bei einem mehrzeiligen Eingabefeld. In diesem Fall wird die value-Eigenschaft des Formularelements (zur Erinnerung: Es steht in der Variablen i) überprüft. Falls keine Eingabe erfolgt, wird das name-Attribut des Eingabefelds (es steht in der Eigenschaft name) dem Fehlerstring hinzugefügt, um wenigstens eine einigermaßen aussagekräftige Fehlermeldung ausgeben zu können.

if (i.type=="text" || i.type=="password" || i.type=="textarea") {
   if (i.value == "") {
      fehler += i.name + "
";
   }
}


9.2.2 Radiobuttons  

Für Radiobuttons (die Sie an der type-Eigenschaft "radio" erkennen) muss man sich eine besondere Strategie überlegen. Jeder Radiobutton ist ein eigenes Formularelement und muss nicht unbedingt ausgewählt sein. Auf die folgende Art und Weise kommt man dennoch zum Ziel:

gpBilder
 
Wenn Sie feststellen, dass das aktuell betrachtete Formularelement ein Radiobutton ist, wird das name-Attribut ausgelesen (Eigenschaft name). Dann wird das dazugehörige Array von Radiobuttons daraufhin durchsucht, ob zumindest einer davon ausgewählt ist. Falls nicht, wird der Fehlerstring um das name-Attribut der Gruppe der Radiobuttons erweitert.
gpBilder
 
Jeder Radiobutton wird als einzelnes Formularelement behandelt. Ist also von einer Gruppe aus fünf Radiobuttons keiner ausgewählt, werden bei der Überprüfung der fünf Radiobuttons insgesamt fünfmal Mängel gefunden – es würde also fünf Fehlermeldungen geben. Aus diesem Grund wird eine weitere Variable, radiocheck, eingeführt (und zwar außerhalb der for-Schleife). In radiocheck werden die name-Attribute bereits geprüfter Radio-Buttons, durch Leerzeichen getrennt, eingetragen. Bevor ein Radiobutton überprüft wird, muss zuerst nachgeschaut werden, ob schon einmal ein anderer Radiobutton aus dieser Gruppe betrachtet worden ist. Daraus folgt auch eine Einschränkung für das Skript: Die name-Attribute der Radiobuttons dürfen keine Leerzeichen enthalten – aber das sollte sich von selbst verstehen; ich verweise auf die Einleitung.
if (i.type=="radio") {
   if (radiocheck.indexOf(i.name + " "<0) {
      radiocheck += i.name + " ";
      eval("var radiogroup = f." + i.name)
      var ok = false;
      for (var k=0; k<radiogroup.length; k++) {
         if (radiogroup[k].checked) {
            ok = true;
         }
      }
      if (!ok) {
         fehler += i.name + "
";
      }
   }
}


9.2.3 Checkboxen  

Bei Checkboxen ist eine allgemeine Überprüfung schlecht möglich. Es ist dem Benutzer ja freigestellt, ob er eine Checkbox ankreuzt oder nicht. Jede Checkbox ist im Grunde genommen eine Ja/Nein-Frage, und somit bedeutet kein Kreuz auch eine Antwort, nämlich Nein. Aus diesem Grund werden Checkboxen (die type-Eigenschaft heißt übrigens "checkbox") nicht überprüft. Ist das dennoch erforderlich, hilft einfach ein Blick auf die Eigenschaft checked: Ist diese false, ist die Checkbox nicht ausgewählt worden. Im endgültigen Skript finden Sie den Code für diese Überprüfung in einkommentierter Form.


9.2.4 Auswahllisten  

Auswahllisten erkennt man daran, dass die type-Eigenschaft des Formularelements entweder den Wert "select-one" oder den Wert "select-multiple" hat, je nachdem, ob der HTML-Parameter multiple gesetzt worden ist oder nicht. Die Eigenschaft selectedIndex muss überprüft werden. Sie muss ungleich 0 sein. Wir nehmen einfach einmal an, dass die oberste Option einer Auswahlliste immer »Bitte wählen Sie« oder ähnlich lautet.

if (i.type=="select-one" || i.type=="select-multiple") {
   if (i.selectedIndex == 0) {
      fehler += i.name + "
";
   }
}


9.2.5 Zusammenfassung 
topBilder

Im Folgenden finden Sie noch einmal den gesamten Code für dieses Beispiel. Mit ihm sind Sie in der Lage, mit JavaScript jedes Formular daraufhin zu überprüfen, ob es vollständig ausgefüllt wurde. In der Praxis kommt es aber oft vor, dass nicht alle Felder Pflichtfelder sind. Viele Benutzer sind auch sehr zurückhaltend, wenn sie allzu persönliche Daten wie etwa Geburtsdatum, E-Mail-Adresse, Telefonnummer oder gar ihr Einkommen angeben müssen. Überlegen Sie sich also ganz genau, ob Sie wirklich alle Daten benötigen.

<html>
<head>
<title>Formularüberprüfung</title>
<script type="text/javascript"><!--
function pruefen(f) {
   var fehler = "";  //Variable für die Fehlermeldung
   var radiocheck = "";  //Variable für überprüfte Radiobuttons
   var i;  //Enthält das jeweilige Formularelement
   for (var j=0; j<f.elements.length; j++) {
      i = f.elements[j];
      //Texteingabefelder
      if (i.type=="text" || i.type=="password" || i.type=="textarea") {
         if (i.value == "") {
            fehler += i.name + "
";
         }
      }
      //Radiobuttons
      if (i.type=="radio") {
         if (radiocheck.indexOf(i.name+ " ") < 0) {
            radiocheck += i.name + " ";
            eval("var radiogroup = f." + i.name);
            var ok = false;
            for (var k=0; k<radiogroup.length; k++) {
               if (radiogroup[k].checked) {
                  ok = true;
               }
            }
            if (!ok) {
               fehler += i.name + "
";
            }
         }
      }

      //Auswahllisten
      if (i.type=="select-one" || i.type=="select-
      multiple") {
         if (i.selectedIndex == 0) {
            fehler += i.name + "
";
         }
      }

      //Checkboxen (einkommentiert)
      //if (i.type=="checkbox") {
      //   if (i.checked == false) {
      //      fehler += i.name + "
";
      //   }
      //}
   }
   //Fehlermeldung
   if (fehler != "") {
      alert("Bitte füllen Sie die folgenden Felder aus:
      
" + fehler);
      return false;
   }
   return true;
}
//--></script>
</head>
<body>
<form onsubmit="return pruefen(this);">
... Formularfelder kommen hierhin ...
</form>
</body>
</html>

.3 Anwendungsmöglichkeiten für Formulare 
topBilder

Neben einer einfachen Überprüfung von Benutzereingaben auf Vollständigkeit können Formulare auch noch anderweitig verwendet werden. In diesem Abschnitt werden einige Anwendungsmöglichkeiten vorgestellt, die Sie vielleicht schon in ähnlicher Form im World Wide Web gesehen haben. Sie können sie direkt auf Ihrer Website einsetzen.


9.3.1 Währungsrechner  

Der Kurs D-Mark/Euro ist ja leider nicht exakt 2:1, so dass ein Umrechner in Zeiten des »Teuro« immer noch ein praktisches Hilfsmittel ist (Marke: »wie billig früher doch alles war«). Das Skript wird folgendermaßen aufgebaut:

gpBilder
 
In einem Texteingabefeld gibt der Benutzer den Betrag in einer Währung ein.
gpBilder
 
In welche Richtung umgerechnet werden soll, also D-Mark in Euro oder umgekehrt, wird mit Radiobuttons ausgewählt.
gpBilder
 
Die Umrechnung wird gestartet, indem eine Schaltfläche angeklickt wird.
gpBilder
 
Der umgerechnete Betrag wird in einem neuen Texteingabefeld angezeigt.

Die größte Schwierigkeit besteht darin, die Eingaben des Benutzers eventuell umzuformen. JavaScript verwendet das amerikanische Zahlenformat, und das kennt nur einen Dezimalpunkt, kein Dezimalkomma. Also wird das erste Komma durch einen Punkt ersetzt. Die Umwandlung der Zeichenkette in eine Dezimalzahl geschieht automatisch durch die Multiplikation mit dem bzw. Division durch den amtlichen Umrechnungskurs. Andernfalls könnten Sie die Funktionen eval() oder parseFloat() verwenden.

<html> <head> <title>Euro-Rechner</title> <script type="text/javascript"><!-- function umrechnen(f) { //f ist eine Referenz //auf das Formular var betrag = f.betrag.value; if (betrag.indexOf(",")!=-1) { betrag = betrag.substring(0, betrag.indexOf(",")) + "." + betrag.substring(betrag.indexOf(",")+1, betrag.length); } if (f.umrechnung[0].checked) { f.ausgabe.value = betrag / 1.95583; } else { f.ausgabe.value = 1.95583 * betrag; } } //--></script> </head> <body> <form> <input type="text" name="betrag" /><br /> <input type="radio" name="umrechnung" value="DMEUR" checked="checked" /> DM nach Euro | <input type="radio" name="umrechnung" value="EURDM" /> Euro nach DM<br /> <input type="button" value="Umrechnen!" onclick="umrechnen(this.form)" /> <input type="text" name="ausgabe" /> </form> </body> </html>

Bilder

Abbildung 9.4     Der Währungsrechner in Aktion

Beachten Sie bei obigem Code den Aufruf umrechnen(this.form). Innerhalb jedes Event-Handlers eines Formularelements bezieht sich this auf das entsprechende Formularelement. Nun haben alle Formularelemente praktischerweise eine Eigenschaft form, die eine Referenz auf das dazugehörige Formular darstellt. Man kann also mit this.form das Formular als Parameter direkt an die Funktion übergeben.


9.3.2 Währungsrechner, Teil 2  

Der Währungsrechner lässt sich mit relativ wenig Aufwand so erweitern, dass auch der Euro-Kurs anderer Währungen ermittelt werden kann. Insbesondere bei international ausgerichteten Webseiten ist das ein netter Service. Die Idee dahinter ist folgende: Die verschiedenen Währungen stehen alle in einer Auswahlliste. Die beiden Radiobuttons sind mit »nach Euro« und »von Euro« beschriftet. Nach einem Klick auf die Schaltfläche wird zunächst das Umtauschverhältnis zwischen Euro und der ausgewählten Währung ermittelt. Dieser Faktor ist in einem Array abgespeichert worden, so dass der Wert von selectedIndex der Auswahlliste direkt als Array-Index verwendet werden kann. Je nachdem, welcher Radiobutton angeklickt worden ist, wird der eingegebene Betrag entweder durch den Umrechnungsfaktor geteilt oder damit multipliziert.

<html> <head> <title>Euro-Rechner Teil 2</title> <script type="text/javascript"><!-- var kurs = new Array(); kurs[0] = 1.95583; kurs[1] = 6.55957; kurs[2] = 1937.98450; function umrechnen(f) { //f ist eine Referenz //auf das Formular var betrag = f.betrag.value; if (betrag.indexOf(",")!=-1) { betrag = betrag.substring(0, betrag.indexOf(",")) + "." + betrag.substring(betrag.indexOf(",")+1, betrag.length); } var faktor = kurs[f.Waehrung.selectedIndex]; if (f.umrechnung[0].checked) { f.ausgabe.value = betrag / faktor; } else { f.ausgabe.value = faktor * betrag; } } //--></script> </head> <body> <form> <input type="text" name="betrag" /> <select name="Waehrung"> <option>DM</option> <option>Franc</option> <option>Lira</option> </select> <br> <input type="radio" name="umrechnung" value="nachEUR" checked="checked" /> nach Euro | <input type="radio" name="umrechnung" value="vonEUR" /> von Euro<br /> <input type="button" value="Umrechnen!" onclick="umrechnen(this.form)" /> <input type="text" name="ausgabe" /> </form> </body> </html>

Natürlich können Sie das Beispiel auch umschreiben, sodass Sie mit dem Euro als Referenzwährung zwischen zwei verschiedenen Währungen umrechnen können. Wenn Sie andere Währungen mit aufnehmen, beispielsweise den US-Dollar, müssen Sie den Kurs möglichst häufig aktualisieren, da er sich permanent ändert.


9.3.3 Formularfelder für die Textausgabe nutzen  

Wenn man Nachrichten für den Benutzer ausgeben will, ist ein Fenster via window.alert() etwas aufdringlich, und auch das Schreiben in die Statuszeile hat seine Nachteile. Ein Texteingabefeld eignet sich hierfür jedoch wunderbar. Zwar sind auch hier die gestalterischen Möglichkeiten eingeschränkt, aber man muss die Nachteile gegeneinander abwägen. Wie der Lese- und Schreibzugriff auf Texteingabefelder vonstatten geht, haben Sie weiter oben in diesem Kapitel gesehen, das ist also kein Problem mehr.

Etwas ärgerlich ist es jedoch, wenn der Benutzer denkt, er müsse in dieses Texteingabefeld etwas schreiben. Aus diesem Grund wäre es wünschenswert, wenn man das Feld mit einem Schreibschutz (für den Benutzer) versehen könnte. Modernere Browser bieten dafür ein eigenes HTML-Attribut an, aber im Sinne einer allgemein verträglichen Lösung muss man sich mit JavaScript behelfen.

Texteingabefelder kennen den Event-Handler onfocus, der genau dann aktiv wird, wenn der Fokus auf dem Texteingabefeld landet (in der Regel sieht man, dass hineingeklickt wird und der Cursor blinkt). Das Gegenteil vom Erhalten des Fokus heißt auf Englisch »to blur« (dt. »verschwimmen«, »undeutlich werden«), und jedes Texteingabefeld besitzt eine Methode namens blur(), die den Fokus entfernt. Durch folgende Zeile wird der Fokus also genau dann von einem Feld weggenommen, wenn es angeklickt wird. Der Benutzer ist also nicht in der Lage, dem Feld permanent den Fokus zu geben und Eingaben zu tätigen.

<input type="text" onfocus="this.blur();" />

Beachten Sie unbedingt die Reihenfolge! Das folgende Kommando würde dafür sorgen, dass ein Feld immer den Fokus erhält, und somit eine weitere Navigation oder Benutzung der Website verhindern. Dem Benutzer würde kaum etwas anderes übrig bleiben, als seinen Browser zu schließen.

<input type="text" onblur="this.focus();" />

Seien Sie ebenfalls vorsichtig, wenn Sie modale Fenster in Verbindung mit onfocus verwenden. Wenn sich ein modales Fenster öffnet, verliert das Texteingabefeld natürlich den Fokus. Es erhält ihn aber wieder, sobald das modale Fenster wieder geschlossen wird. Der folgende Code zwingt den Benutzer wahrscheinlich dazu, den Browser über das System »abzuschießen«, da sich immer wieder ein modales Fenster öffnet:

<input type="text" onfocus="windows.alert('Die Geister, die ich rief ...');" />


9.3.4 Navigation mit Auswahllisten 
topBilder

Links benötigen viel Platz. Eine Auswahlliste ist da sehr sparsam, kann hinter dem sparsamen Äußeren jedoch eine Menge Inhalt verbergen. So ist es schon auf vielen Webseiten üblich, in einer Auswahlliste mehrere Navigationsziele anzugeben. Sobald Sie einen Punkt in der Auswahlliste anklicken, wird die entsprechende Seite aufgerufen.

Dazu benötigt man einen Event-Handler, der dann aktiv wird, wenn sich die aktuelle Auswahl der Auswahlliste ändert. Dieser Event-Handler heißt onchange und wird von allen Browsern unterstützt.

Im unteren Beispiel wird wieder eine Referenz auf das aktuelle Formular an die JavaScript-Funktion übergeben. Die URLs werden in einem Array gespeichert.

<html> <head> <title>Navigation mit JavaScript</title> <script type="text/javascript"><!-- var urls = new Array(); urls[0] = ""; //leere URL für oberste Option urls[1] = "http://www.mozilla.com/ "; urls[2] = "http://www.microsoft.com/germany"; urls[3] = "http://www.opera.com/"; function laden(f) { if (f.auswahl.selectedIndex > 0) { location.href = urls[f.auswahl.selectedIndex]; } } //--></script> </head> <body> <h1>Navigation mit JavaScript</h1> <form> <select name="auswahl" onchange="laden(this.form)"> <option>Bitte wählen</option> <option>Mozilla</option> <option>Microsoft</option> <option>Opera</option> </select> <input type="button" value="Laden" onclick="laden(this.form)" /> </form> </body> </html>

Sie wissen ja bereits, wie Sie mit JavaScript allgemein auf Formulardaten zugreifen können. Im weiteren Verlauf dieses Kapitels werden einige fortgeschrittenere Techniken dargestellt, wie man Formulare sinnvoll nutzen kann. Neben neuen Möglichkeiten der Formularüberprüfung lernen Sie hier auch einige Tricks und Kniffe, die in der täglichen Praxis von Nutzen sein können.

 

9.4 Anwendungsbeispiel: Fensteroptionen 
topBilder
topBilder

Im Folgenden soll ein Skript entwickelt werden, das versucht, dem Programmierer einen Überblick über die zahlreichen Fensteroptionen zu geben. Der Benutzer kann dazu die Optionen einzeln ein- und ausschalten und sich so bequem eine Reihe von neuen Fenstern erzeugen lassen. Das ist nicht nur in Hinblick auf das vorherige Kapitel praktisch, sondern zeigt auch den Zugriff auf Formularelemente auf.

Dazu wird zunächst einmal eine Reihe von Formularfeldern benötigt. Für die booleschen Optionen setzen wir Radiobuttons ein, die jeweils als name-Attribut den Namen der Option tragen und als value-Attribut den gewünschten Wert. Das erleichtert später das Zusammensetzen des Optionsstrings.

location
  <input type="radio" name="location" value="yes" />yes
  <input type="radio" name="location" value="no" />no

Bei numerischen Werten wird ein Textfeld angeboten:

height <input type="text" name="height" />

Für den Fall, dass Mozilla-kritische Optionen verwendet werden, die zusätzliche Rechte benötigen, kann per Mausklick angegeben werden, dass der Privilege Manager aktiviert werden soll:

Privilege Manager aktivieren
  <input type="checkbox" name="PrivilegeManager" value="on" />

Sobald das Formular verschickt wird, soll der Optionsstring zusammengesetzt werden. Nun könnte zwar jedes einzelne Formularfeld »hartkodiert« über seinen Namen abgefragt werden, dadurch wird aber das Skript in Hinblick auf mögliche (aber unwahrscheinliche) zukünftige Erweiterungen der Optionen unflexibel. Aus diesem Grund ist es sinnvoll, das Skript möglichst allgemein zu halten und per Schleife alle Formularfelder zu untersuchen:

for (var i=0; i<f.elements.length; i++) {
   // ...
}

Zwar würde theoretisch auch Folgendes funktionieren:

for (var e in f.elements) {
   // ...
}

Der Internet Explorer interpretiert dann aber die Variable e anders als der Mozilla, während letzterer dafür einige Formularelemente doppelt berücksichtigt. Das macht diese Schleife natürlich für diese Zwecke unbrauchbar.

Nun wird die type-Eigenschaft des Formularelements untersucht. Von besonderem Interesse sind hierbei Textfelder (type == "text"), die die Werte der numerischen Optionen enthalten, sowie Radiobuttons (type == "radio"), die für die booleschen Optionen zuständig sind.

Bei den Textfeldern werden all diejenigen Textfelder verwendet, die gefüllt sind, also als Wert etwas anderes als "" aufweisen:

if (e.type == "text" && e.value != "") {
   optionen += "," + e.name + "=" + e.value;
}

Bei Radiobuttons muss überprüft werden, ob sie aktiviert sind (die Eigenschaft checked muss true sein) oder nicht.

if (e.type == "radio" && e.checked == true) {
   optionen += "," + e.name + "=" + e.value;
}

Der Optionsstring wird dabei immer um ,name=wert erweitert. Am Ende des Vorgangs beginnt der Optionsstring mit einem Komma, das entfernt werden muss:

if (optionen != "") {
   optionen = optionen.substring(1, optionen.length);
}

Als Nächstes wird überprüft, ob die Checkbox für den Privilege Manager aktiviert ist. Falls ja, fordert das Skript die entsprechenden Rechte an:

if (f.elements["PrivilegeManager"].checked) {
   netscape.security.PrivilegeManager.enablePrivilege(
      "UniversalBrowserWrite");
}

Schließlich wird die eigentliche Hauptfunktion aufgerufen: window .open(). Im neuen Fenster soll das eigentlich Interessante, der Optionsstring angezeigt werden. Um dies zu realisieren, gibt es mehrere Möglichkeiten. Eine davon wäre, dass man als URL des neuen Fensters eine JavaScript-Funktion angibt, die den Text ausgibt.

javascript:document.write(optionen);document.close()

Innerhalb des Skripts müssen Sie noch darauf achten, dass Sie in der URL nicht die Variable optionen verwenden, sondern deren Wert, denn im neuen Fenster ist diese Variable unbekannt.

var w = window.open("javascript:document.write("" +
   optionen + "");document.close()", "test", optionen);

Alternativ können Sie auch das Fenster ohne Inhalt öffnen und den Inhalt dann mit document.open(), document.write() und document.close() erzeugen:

var w = window.open("");
if (w) {
   with (w.document) {
      open();
      write(optionen);
      close();
   }
}

Als Nächstes erhält das Fenster den Fokus, sollte es hinter einem anderen Fenster stehen. Auch hier wird wieder zunächst überprüft, ob das Öffnen des Fensters funktioniert hat – mit if (w).

if (w) {
   w.focus();
}

Falls der Privilege Manager zuvor eingeschaltet worden ist, kann er nun wieder deaktiviert werden. Dieses Vorgehen empfiehlt sich grundsätzlich, denn die zusätzlichen Rechte sollten Sie so selten wie möglich anfordern, und wenn Sie es tun, dann sollten Sie sie bald wieder abgeben. Das hat nicht nur Performance-Gründe.

if (f.elements["PrivilegeManager"].checked) {
   netscape.security.PrivilegeManager.disablePrivilege(
      "UniversalBrowserWrite");
}

Als Besonderheit wird der Optionsstring noch im ursprünglichen Browserfenster ausgegeben. Dazu wird ein <div>-Container erstellt:

<div id="optionen" style="position: absolute;">
</div>

Nun wird – mit ein wenig DHTML – in diesen Container hineingeschrieben. Details hierzu erfahren Sie in Kapitel 17:

document.getElementById("optionen").innerHTML = optionen;

Doch nun genug der langen Vorbereitungen: Hier folgt das komplette Skript, wie Sie es natürlich auch auf der DVD-ROM zum Buch finden. Im Vergleich zu den obigen Ausführungen wurde es nur an einigen Stellen kosmetisch verschönert.

<html>
<head>
<title>Fenstergenerator</title>
<script type="text/javascript"><!--
function generiere_fenster(f) {
   var optionen = "";
   var e;
   for (var i=0; i<f.elements.length; i++) {
     e = f.elements[i];
     if (e.type == "text" && e.value != "") {
        optionen += "," + e.name + "=" + e.value;
     } else if (e.type == "radio" && e.checked == true) {
        optionen += "," + e.name + "=" + e.value;
     }
   }
   if (optionen != "") {
      optionen = optionen.substring(1, optionen.length);
   }

   if (f.elements["PrivilegeManager"].checked) {
      netscape.security.PrivilegeManager.enablePrivilege(
         "UniversalBrowserWrite");
   }
   var w = window.open("javascript:document.write(""+
      optionen + "");document.close()", "test", optionen);
   if (w && w.focus) {
      w.focus();
   }
   if (f.elements["PrivilegeManager"].checked) {
      netscape.security.PrivilegeManager.disablePrivilege(
         "UniversalBrowserWrite");
   }

   if (document.getElementById) {
      document.getElementById("optionen").innerHTML =
         optionen;
   }
}
//--></script>
</head>
<body>
<form onsubmit="return false;">
<table cellspacing="10">
<tr><td>
<table cellspacing="5">
<tr><td>
<i>alwaysLowered</i>
</td><td>
  <input type="radio" name="alwaysLowered" value="yes" />yes
  <input type="radio" name="alwaysLowered" value="no" />no
</td></tr>
<tr><td>
<i>alwaysRaised</i>
</td><td>
  <input type="radio" name="alwaysRaised" value="yes" />yes
  <input type="radio" name="alwaysRaised" value="no" />no
</td></tr>
<tr><td>
channelMode
</td><td>
  <input type="radio" name="channelMode" value="yes" />yes
  <input type="radio" name="channelMode" value="no" />no
</td></tr>
<tr><td>
dependent
</td><td>
  <input type="radio" name="dependent" value="yes" />yes
  <input type="radio" name="dependent" value="no" />no
</td></tr>
<tr><td>
directories
</td><td>
  <input type="radio" name="directories" value="yes" />yes
  <input type="radio" name="directories" value="no" />no
</td></tr>
<tr><td>
fullscreen
</td><td>
  <input type="radio" name="fullscreen" value="yes" />yes
  <input type="radio" name="fullscreen" value="no" />no
</td></tr>
<tr><td>
hotkeys
</td><td>
  <input type="radio" name="hotkeys" value="yes" />yes
  <input type="radio" name="hotkeys" value="no" />no
</td></tr>
<tr><td>
location
</td><td>
  <input type="radio" name="location" value="yes" />yes
  <input type="radio" name="location" value="no" />no
</td></tr>
<tr><td>
menubar
</td><td>
  <input type="radio" name="menubar" value="yes" />yes
  <input type="radio" name="menubar" value="no" />no
</td></tr>
<tr><td>
personalbar
</td><td>
  <input type="radio" name="personalbar" value="yes" />yes
  <input type="radio" name="personalbar" value="no" />no
</td></tr>
<tr><td>
resizable
</td><td>
  <input type="radio" name="resizable" value="yes" />yes
  <input type="radio" name="resizable" value="no" />no
</td></tr>
<tr><td>
scrollbars
</td><td>
  <input type="radio" name="scrollbars" value="yes" />yes
  <input type="radio" name="scrollbars" value="no" />no
</td></tr>
<tr><td>
status
</td><td>
  <input type="radio" name="status" value="yes" />yes
  <input type="radio" name="status" value="no" />no
</td></tr>
<tr><td>
<i>titlebar</i>
</td><td>
  <input type="radio" name="titlebar" value="yes" />yes
  <input type="radio" name="titlebar" value="no" />no
</td></tr>
<tr><td>
toolbar
</td><td>
  <input type="radio" name="toolbar" value="yes" />yes
  <input type="radio" name="toolbar" value="no" />no
</td></tr>
<tr><td>
<i>z-lock</i>
</td><td>
  <input type="radio" name="z-lock" value="yes" />yes
  <input type="radio" name="z-lock" value="no" />no
</td></tr>
</table>
</td><td>
<table cellspacing="5">
<tr><td>height </td>
  <td><input type="text" name="height" /></td></tr>
<tr><td>innerHeight </td>
  <td><input type="text" name="innerHeight" /></td></tr>
<tr><td>innerWidth </td>
  <td><input type="text" name="innerWidth" /></td></tr>
<tr><td>left </td>
  <td><input type="text" name="left" /></td></tr>
<tr><td>outerHeight </td>
  <td><input type="text" name="outerWidth" /></td></tr>
<tr><td>outerWidth </td>
  <td><input type="text" name="outerWidth" /></td></tr>
<tr><td>screenX </td>
  <td><input type="text" name="screenX" /></td></tr>
<tr><td>screenY </td>
  <td><input type="text" name="screenY" /></td></tr>
<tr><td>top </td>
  <td><input type="text" name="top" /></td></tr>
<tr><td>width </td>
  <td><input type="text" name="width" /></td></tr>
</table>
</td></tr>
<tr><td colspan="2">
Privilege Manager aktivieren
  <input type="checkbox" name="PrivilegeManager" value="on" />
</td></tr>
<tr><td colspan="2">
<input type="button" value="Generieren!"
       onclick="generiere_fenster(this.form);" />
</td></tr>
</table>
</form>
<div id="optionen" style="position: absolute;">
</div><br />
</body>
</html>

Bilder

Abbildung 9.5     Der Fenstergenerator samt neuem, generiertem Fenster

 

9.5 Daten behalten 
topBilder

Im vorangegangenen Kapitel haben Sie gesehen, wie man Daten in Cookies speichern kann und somit beispielsweise einen JavaScript-Warenkorb ohne Frames realisieren kann. Der Nachteil wurde auch nicht verschwiegen: Während die meisten Benutzer JavaScript nicht deaktivieren, schalten viele User die Verwendung von Cookies aus, natürlich auch bedingt durch Horrormeldungen in der Presse. Es gibt jedoch auch Möglichkeiten, ohne Cookies Daten von einer HTML-Seite in die nächste zu übernehmen. Eine der Alternativen besteht in der Verwendung von Frames und in der Datenspeicherung in einem davon; hier sei auf Kapitel 10 verwiesen. In diesem Abschnitt wird eine weitere Möglichkeit vorgestellt.

Ein virtueller Geschäftsmann will den Besuchern seiner Website Bücher in elektronischer Form zum Download anbieten. Er hat eine ganze Reihe davon, und so soll der Benutzer in einem Formular die für ihn interessanten Programme auswählen können. Nach dem Absenden des Formulars erhält er Links auf die ausgewählten Programme. Bevor Sie das Beispiel anpassen und einsetzen, sollten Sie allerdings etwaige Copyright-Fragen klären.


9.5.1 Das Eingabeformular  

Das Formular selbst ist schnell erstellt; als zusätzlicher Bonus werden dem Benutzer zu jedem Programm Hintergrundinformationen zu jedem Produkt in einem neuen Fenster angezeigt. Wie das prinzipiell funktioniert, wurde schon in Kapitel 8 gezeigt. Beachten Sie im Folgenden, dass jeder Checkbox konsistent das value-Attribut "on" zugewiesen worden ist. Der Sinn dieser Maßnahme wird weiter unten klar.

Bilder

Abbildung 9.6     Das HTML-Formular

<html>
<head>
<title>Bücher-Download</title>
<script type="text/javascript"><!--
function hilfe(seite) {
   window.open(seite,
      "hilfefenster","height=300,width=400");
}
//--></script>
</head>
<body>
<h1>Wählen Sie hier Ihre Bücher aus!</h1>
<form method="get" action="download.html">
<input type="checkbox" name="sommer" value="on" />
   <a href="javascript:hilfe('sommer.html')">
   Sommernachtstraum</a><br />
<input type="checkbox" name="othello" value="on" />
   <a href="javascript:hilfe('othello.html')">Othello</a>
   <br />
<input type="checkbox" name="venedig" value="on" />
   <a href="javascript:hilfe('venedig.html')">Der
   Kaufmann von Venedig</a><br />
...<br />
<input type="submit" value="Zum Download" />
</form>
</body>
</html>


9.5.2 Die Ausgabeseite 
topBilder

Wenn Sie das Formular auf der vorangegangenen Seite genauer betrachtet haben, wird Ihnen folgende Zeile nicht entgangen sein:

<form method="get" action="download.html">

Zum einen ist der Wert des action-Attributs ungewöhnlich – seit wann übergibt man Formularwerte an eine HTML-Seite? Zum anderen ist method auf "get" gesetzt. Dazu ein kleiner Exkurs: Mit method wird die Versendemethode des Formulars angegeben. Standardmäßig – also auch, wenn nichts angegeben wird – wird die Methode GET verwendet, doch häufiger ist POST. In letzterem Fall werden die Formulareingaben im Inhalt der Anforderung, also nach dem HTTP-Header versandt. Die entsprechende HTTP-Anforderung des Webbrowsers an den Webserver sieht ungefähr folgendermaßen aus:

POST /download.html HTTP/1.1
Content-type: application/x-www-form-urlencoded
Accept: text/plain
Content-length: 21

othello=on&venedig=on

Sie sehen, dass die einzelnen (angekreuzten) Checkboxen im Formular name=value übergeben und voneinander mit dem kaufmännischen Und (&) getrennt werden.

Wenn wie im obigen Beispiel die Methode GET verwendet wird, sieht die Sache anders aus: Hier werden nämlich die Formulardaten als Teil der URL übergeben. Es würde folgende Seite aufgerufen werden:

download.html?othello=on&venedig=on

Diese Daten kann man mit JavaScript abfragen. Wie Sie sich vielleicht erinnern, erhält man mit location.search den Teil der URL ab dem Fragezeichen, in diesem Fall also ?othello=on&venedig=on. Mit JavaScript können diese Daten zerlegt werden, um die Ausgabeseite zu generieren.

Um die ganze Sache ein wenig zu vereinfachen, gehen wir davon aus, dass alle Bücher im Verzeichnis download liegen, die Dateiendung .zip haben und dass sich der Teil vor der Endung aus dem name-Attribut der zugehörigen Checkbox ergibt. Der Programmierer muss also nur noch folgende Aufgaben erledigen:

gpBilder
 
Er muss die Eigenschaft location.search auslesen und das erste Zeichen (das Fragezeichen) abschneiden.
gpBilder
 
Er muss die übrig bleibende Zeichenkette an den kaufmännischen Unds aufteilen. Die einzelnen Teile haben dann den Aufbau buchname=on, und wenn man das =on entfernt, hat man den Dateinamen ohne die Endung.

Man kann den Algorithmus ein wenig vereinfachen, indem man einfach nach dem nächsten Vorkommen von & sucht sowie nach dem nächsten Vorkommen von =on und dann die Zeichenkette dazwischen betrachtet. Die Zeichenkette =on kann übrigens an keiner anderen Stelle (also zum Beispiel links vom Gleichheitszeichen) in der URL erscheinen. Der Grund dafür ist, dass Sonderzeichen in ein URL-konformes, hexadezimales Format umgewandelt werden. Aus einem Leerzeichen wird %20, und aus einem Gleichheitszeichen wird %3D. 3D ist nämlich die hexadezimale Darstellung der Dezimalzahl 62, und im ASCII-Zeichensatz hat das Gleichheitszeichen den Code 62. Der ASCII-Code des Leerzeichens ist 32, und die hexadezimale Darstellung ist 20. Die Umwandlung in dieses URL-Format geschieht automatisch; die Rückverwandlung erfolgt mit der JavaScript-Funktion unescape(). So liefert beispielsweise unescape("%3D") das Ergebnis "=" zurück. Die Umwandlung ist im Übrigen auch mit JavaScript möglich, escape("=") liefert "%3D".

Der Internet Explorer ab 5.5 bietet hierzu übrigens ein paar neue Funktionen an, die eine URI (Unified Ressource Identificator, eine Art Obermenge von URL) erzeugen können. Die Funktion encodeURI() wandelt den übergebenen Parameter in eine URL um, also liefert beispielsweise encodeURI("meine Webseite.html#oben") als Ergebnis meine%20Webseite.html#oben zurück. Sie sehen hier, dass das Doppelkreuz als Abtrennungszeichen für einen HTML-Anker interpretiert worden ist, also insbesondere nicht in die hexadezimale Form %23 umgewandelt wurde. Wenn Sie auch das möchten (beispielsweise, wenn Sie die obige URL als Parameter an ein CGI-Skript übergeben möchten), müssen Sie encodeURIComponent() verwenden. Dies liefert das gewünschte Ergebnis: encodeURIComponent("meine Webseite.html#oben") gibt meine%20 Webseite.html%23oben zurück.

Die entsprechenden Funktionen, die eine codierte URL wieder entschlüsseln, heißen decodeURI() und decodeURIComponent(). Und wie erwartet gibt decodeURI("meine%20Webseite.html#oben") als Ergebnis meine-Webseite.html#oben zurück, decodeURIComponent("meine%20Webseite .html%23oben") liefert ebenfalls meine Webseite.html#oben.

Aus den viel zitierten Gründen der Abwärtskompatibilität müssen Sie jedoch noch eine ganze Weile auf den Einsatz dieser Funktionen verzichten, wenn Sie möglichst viele Browser unterstützen wollen.

Zurück zur Aufgabenstellung: Der folgende Code (zu speichern als download.html) liest die übergebenen Daten aus und generiert die Ausgabeseite.

<html>
<head>
<title>Download Seite 2</title>
<script type="text/javascript"><!--
function tag(s) {
   return("<" + s + ">");
}
function ausgabe() {
   var ls = location.search;
   var txt = (ls.length>1) ? "Sie können die Bücher nun
     herunterladen" : "Keine Bücher ausgewählt";
   document.write(txt + tag("br"));
   if (ls.length<=1) { //Funktion verlassen, falls
                       //nichts ausgewählt wurde
      return false;
   }
   ls = "&" + ls.substring(1, ls.length);
      //Fragezeichen entfernen, "&" vorne anhängen
   var pos = 0;  //aktuelle Suchposition in
   location.search
   while (pos == 0 || ls.indexOf("&", pos) != –1) {
      start = ls.indexOf("&", pos) + 1;
      ende = ls.indexOf("=on", pos);
      buch = unescape(ls.substring(start, ende));
      document.write(tag("a href='download/" + buch
         + ".zip'"));
      document.write(buch);
      document.write(tag("/a")+tag("br"));
      pos = ende + 2;
   }
}
//--></script>
</head>
<body>
<h1>Bücher-Download</h1>
<script type="text/javascript"><!--
ausgabe();
//--></script>
<noscript>
Ihr Browser unterstützt kein JavaScript!
</noscript>
</body>
</html>

Bilder

Abbildung 9.7     Die Download-Seite

 

9.6 Dynamische Auswahllisten 
topBilder

In Kapitel 11 werden Sie ein Beispiel mit einer grafischen Navigation finden. Dort gibt es mehrere Kategorien, die aufgrund einer Mausbewegung ausklappen und Unterpunkte freigeben. Dies lässt sich sehr platzsparend auch in einem Formular realisieren. Es ist insbesondere möglich, via JavaScript einzelne Punkte in den Auswahllisten zu ändern, zu löschen und neue Punkte hinzuzufügen.


9.6.1 Ein erster Ansatz  

Wie Sie wissen, werden die einzelnen Optionen einer Auswahlliste im Array options gespeichert. Jede einzelne dieser Optionen hat die Eigenschaft text, die den im Browser angezeigten Text angibt.

Das ist an sich kein Problem, aber nicht alle Kategorien haben die gleiche Anzahl von Unterpunkten. Da jedoch die einzelnen Optionen in einem Array gespeichert werden, liegt es nahe, die Eigenschaft length dieses Arrays zu ändern. Einen ersten Ansatz für die Navigation finden Sie im folgenden Listing. Die linke Auswahlliste enthält die Kategorien, und wenn eine davon ausgewählt wird (Event-Handler: onchange), werden die Untermenüpunkte in der rechten Auswahlliste angezeigt. Sobald dort ein Punkt ausgewählt wird, kann die entsprechende Webseite geladen werden. Wie im Beispiel aus Kapitel 11 stehen die wichtigen Variablen – in diesem Falle die URLs und die Namen der einzelnen Menüpunkte – am Anfang des Skripts in globalen Variablen, damit sie einfach angepasst werden können.

<html>
<head>
<title>Navigation mit Auswahllisten – Teil 1</title>
<script type="text/javascript"><!--
// *** globale Variablen
var urls = new Array(
   new Array(""),
   new Array("", "seite1–1.html", "seite1–2.html", "seite1–3.html"),
   new Array("", "seite2–1.html", "seite2–2.html"),
   new Array("", "seite3–1.html", "seite3–2.html", "seite3–3.html", "seite3–4.html")
);
var beschriftung = new Array(
   new Array("Bitte auswählen", ""),
   new Array("Bitte auswählen", "Punkt 1.1", "Punkt 1.2", "Punkt 1.3"),
   new Array("Bitte auswählen", "Punkt 2.1", "Punkt 2.2"),
   new Array("Bitte auswählen", "Punkt 3.1", "Punkt 3.2", "Punkt 3.3", "Punkt 3.4")
);
// *** Ende der globalen Variablen
function kategorie_anzeigen(f) {
   var kategorie = f.kategorien.selectedIndex;
   f.unterpunkte.options.length = urls[kategorie].length;
   for (var i=0; i<urls[kategorie].length; i++) {
      f.unterpunkte.options[i].text =
         (beschriftung[kategorie])[i];
   }
}
function seite_laden(f){
   var kategorie = f.kategorien.selectedIndex;
   var unterpunkt = f.unterpunkte.selectedIndex;
}
//--></script>
</head>
<body>
<h1>Navigation mit Auswahllisten</h1>
<form>
<select name="kategorien"
onchange="kategorie_anzeigen(this.form);">
   <option>Bitte auswählen</option>
   <option>Kategorie 1</option>
   <option>Kategorie 2</option>
   <option>Kategorie 3</option>
</select>
<select name="unterpunkte"
onchange="seite_laden(this.form);">
   <option>Bitte auswählen</option>
</select>
</form>
</body>
</html>

Abbildung 9.8     Die beiden Auswahllisten

Bilder

Abbildung 9.9     Das Scrollen der rechten Auswahlliste ist (in alten Browsern) schwierig.

Wenn Sie dieses Beispiel im Browser ausprobieren, werden Sie allerdings wahrscheinlich einen kleinen Nachteil beobachten: Wenn Sie das zweite Auswahlmenü ausklappen wollen, werden Sie – bei manchen Browsern und Betriebssystemen – interessante grafische Nebeneffekte feststellen (siehe Abbildung 9.9). Um diese Effekte zu vermeiden, muss das obige Skript etwas sauberer programmiert werden.


9.6.2 Ein fortgeschrittener Ansatz 
topBilder

Um das Beispiel ganz sauber zu programmieren, sollte man zuerst alle Optionen in der Auswahlliste löschen und dann die Optionen hinzufügen. Für Menüoptionen gibt es einen eigenen Konstruktor, new Option ("Beschriftung", "value-Attribut").

Eine einzelne Option kann gelöscht werden, indem das entsprechende Element im Array options auf null gesetzt wird. Mit folgendem Kommando wird die zweite Option in einer Auswahlliste gelöscht:

document.forms[0].auswahlliste.options[1] = null;

Sie können sich den Aufwand jedoch sparen, wenn Sie die Eigenschaft length des Arrays options auf 0 setzen. Dann können die Optionen erzeugt werden. Die Eigenschaft length wird hierbei automatisch angepasst. Es genügt, wenn Sie die Funktion kategorie_anzeigen() folgendermaßen abändern:

function kategorie_anzeigen(f) {
   var kategorie = f.kategorien.selectedIndex;
   f.unterpunkte.options.length = 0;
   for (var i=0; i<urls[kategorie].length; i++) {
      f.unterpunkte.options[i] =
        new Option((beschriftung[kategorie])[i], "");
   }
}

Außerdem hilft es, pro forma in die linke Auswahlliste ein paar freie leere Felder einzufügen; vor allem der Netscape Navigator führt das Skript dann zuverlässiger aus. Wie Sie sehen, umgehen Sie so grafische Unfälle.

 

9.7 Überprüfungsfunktionen 
topBilder

In Kapitel 8 wurden Formulare auf Vollständigkeit überprüft. In der Praxis gibt es jedoch noch weitere Anforderungen. Manche Eingabefelder verlangen besondere Eingaben, beispielsweise Postleitzahlen, Telefonnummern oder Uhrzeiten. In diesem Abschnitt werden einige Funktionen aus diesem Bereich entwickelt.


9.7.1 Ganze Zahlenwerte  

In JavaScript gibt es die Funktion isNaN(), die feststellt, ob ein Eingabewert keine Zahl ist. Aus Gründen der Flexibilität könnte man aber auch auf die Idee kommen, diese Funktion selbst zu programmieren. Der Eingabewert wird dazu als Zeichenkette betrachtet, und bei jedem Zeichen wird überprüft, ob es eine Ziffer von 0 bis 9 ist. Sollte das für alle Zeichen zutreffen, handelt es sich um eine Zahl, ansonsten liegt keine Zahl vor. Wir haben eine derartige Funktion schon im vorigen Kapitel verwendet, hier nun noch einmal zur Wiederholung:

function isANumber(n) {
   var s = "" + n; //Umwandlung in eine Zeichenkette
   var ziffern = "0123456789";  //Gültige Zeichen
   for (var i=0; i<s.length; i++) {
      if (ziffern.indexOf(s.charAt(i)) == –1) {
      //keine Ziffer
         return false;
      }
   }
   return true;
}

Mit dieser Methode kann man beispielsweise überprüfen, ob ein Eingabewert eine deutsche Postleitzahl darstellen könnte:

if (eingabe.length==5 && isANumber(eingabe))

Streng genommen sind aber nicht alle Postleitzahlen natürliche Zahlen, so beispielsweise 01234. Das ist die Postleitzahl einiger Postfächer in Dresden, aber keine natürliche Zahl, da sie mit 0 beginnt, was nicht sein darf (Ausnahme: die Null selbst). Für streng numerische Eingaben (beispielsweise für ein Anzahl-Feld in einem Bestellformular) muss man die Funktion folgendermaßen umschreiben:

function isANumber2(n) {
   var s = "" + n;  //Umwandlung in eine Zeichenkette
   var ziffern = "0123456789";  //Gültige Zeichen
   if (s == "0") { //Bei "0"
      return true;
   }
   if (s.charAt(0) == "0") { //Bei 0 am Anfang
      return false;
   }
   for (var i=0; i<s.length; i++) {
      if (ziffern.indexOf(s.charAt(i)) == –1) {
      //keine Ziffer
         return false;
      }
   }
   return true;
}

Im dritten Schritt soll noch die zusätzliche Funktionalität implementiert werden, dass auch negative Zahlen erkannt werden, beispielsweise »-42«. Die Überprüfung gestaltet sich jedoch sehr einfach. Ist das erste Zeichen ein Minus, wird es einfach abgetrennt. Hinter dem Minuszeichen dürfen nur noch Ziffern stehen, also bleibt alles wie gehabt.

function isANumber3(n) {
   var s = "" + n;  //Umwandlung in eine Zeichenkette
   var ziffern = "0123456789":  //Gültige Zeichen
   if (s.charAt(0) == "-") { //führendes Minus entfernen
      s = s.substring(1, s.length);
   }
   if (s=="0") { //Bei "0"
      return true;
   }
   if (s.charAt(0) == "0") { //Bei 0 am Anfang
      return false;
   }
   for (var i=0; i<s.length; i++) {
      if (ziffern.indexOf(s.charAt(i)) == –1) {
      //keine Ziffer
         return false;
      }
   }
   return true;
}


9.7.2 Dezimalzahlen  

Das Überprüfen von Dezimalzahlen ist schon eine etwas anspruchsvollere Aufgabe. Zum einen gibt es den Unterschied zwischen der deutschen und der amerikanischen Schreibweise: Während man in Deutschland den Ganzzahlanteil vom Dezimalteil durch ein Komma trennt, verwendet man in Amerika einen Punkt. JavaScript benötigt für Rechnungen die amerikanische Notation, so dass als Allererstes in einer Überprüfungsfunktion alle Kommata durch Punkte ersetzt werden. Zum anderen darf in der vermeintlichen Dezimalzahl maximal ein Punkt (bzw. Komma) vorkommen.

function isAFract(n) {
   var s = "" + n;  //Umwandlung in String
   while (s.indexOf(",") > –1) { //Kommata durch Punkte ersetzen
      s = s.substring(0, s.indexOf(",")) + "." +
         s.substring(s.indexOf(",") + 1, s.length)
   }
   var anzahl_punkt = 0;  //Variable zum Zählen der Dezimalpunkte
   for (var i=0; i<s.length; i++) {
      if (s.charAt(i) == ".") {
         anzahl_punkt++;
      }
   }
   if (anzahl_punkt > 1) { //Mehr als ein Dezimalpunkt?
      return false;
   }
   // *** Eigentliche Überprüfung nach obigem Muster
   // *** Neu: Der Dezimalpunkt ist ein erlaubtes Zeichen
   var ziffern = ".0123456789";  //Gültige Zeichen
   if (s.charAt(0) == "-") { //Führendes Minus entfernen
      s = s.substring(1, s.length);
   }
   if (s == "0") { //Bei "0"
      return true;
   }
   if (s.charAt(0) == "0" && s.charAt(1) != ".") {
      //Bei 0 am Anfang ohne folgenden Punkt
      return false,
   }
   for (var i=0; i<s.length; i++) {
      if (ziffern.indexOf(s.charAt(i)) == –1) {
      //Keine Ziffer
         return false;
      }
   }
   return true;
}


9.7.3 Telefonnummern  

Es gibt viele Möglichkeiten, eine Telefonnummer zu schreiben. Eine Telefonnummer mit der Vorwahl 0123 und der Rufnummer 456789 kann beispielsweise folgendermaßen geschrieben werden:

gpBilder
 
(0123) 456789
gpBilder
 
(01 23) 45 67 – 89
gpBilder
 
01234/456789
gpBilder
 
+49[0]123 – 456789

Zwar können Sie für jeden dieser Fälle eine eigene Überprüfungsfunktion schreiben, aber es ist effizienter, einfach eine Liste der wahrscheinlich vorkommenden Zeichen zu erstellen und darauf zu überprüfen. Eine mögliche Validierungsfunktion kann dann so aussehen:

function isAPhoneNumber(n) {
   var s = "" + n;  //Umwandlung in eine Zeichenkette
   var zeichen = "0123456789+-()[]/ ";  //Gültige Zeichen
   for (var i=0; i<s.length; i++) {
      if (zeichen.indexOf(s.charAt(i)) == –1) {
         //kein gültiges Zeichen
         return false;
      }
   }
   return true;
}


9.7.4 E-Mail-Adressen  

Die Überprüfung von E-Mail-Adressen ist eine klassische Anwendung im Web – aber meiner Meinung nach etwas überbewertet: Allerorten findet man mehr oder minder komplexe Überprüfungsfunktionen für E-Mail-Adressen, doch die meisten haben einige erhebliche Nachteile: Oftmals nämlich werden gültige E-Mail-Adressen abgewiesen, wenn sie seltene, aber gültige Zeichen enthalten. »Klassiker« sind beispielsweise das Plus-Zeichen im Teil vor dem Klammeraffen oder seit einiger Zeit Umlaute und andere Sonderzeichen im Domainnamen.

Absichtliche Falscheingaben eines Nutzers können Sie eh nicht abfangen; unabsichtliche dagegen häufig schon. Prüfen Sie deswegen nur das Nötigste. Wie wäre es etwa, wenn Sie bei Mailadressen lediglich nachsehen, ob es darin einen Klammeraffen und nach diesem irgendwo einen Punkt gibt?

function isEmail(n) {
   var klammeraffe = s.indexOf("@");
   if (klammeraffe < 1) {
      return false;
   } else {
      var punkt = s.substring(klammeraffe).indexOf(".");
      if (punkt < 2) {
         return false;
      } else {
         return true;
      }
   }
}


9.7.5 In Zahlenwerte umwandeln 
topBilder

Wenn man mit Formularen rechnet, ist es oft nicht nur wichtig, Formulareingaben auf numerische Werte zu überprüfen, sondern auch, diese numerischen Werte zu erhalten. Zwar gibt es die Funktionen parseFloat ("Zeichenkette") für Dezimalzahlen und parseInt("Zeichenkette") für ganze Zahlen, aber diese liefern gegebenenfalls ein NaN-Objekt zurück: Not A Number. Zur Überprüfung des Rückgabewerts von parseFloat() und parseInt() würde man die Funktion isNaN() benötigen, und diese hat ja die eingangs erläuterten Nachteile. Außerdem funktioniert parseFloat() nur mit amerikanisch formatierten Eingabewerten, also mit einem Dezimalpunkt statt einem Dezimalkomma. Im Folgenden soll eine neue Funktion programmiert werden, die der Funktion eval() ähnelt, einen Eingabewert überprüft und einen numerischen Wert zurückgibt. Ist der Eingabewert kein korrekt formatierter Zahlenwert, so wird 0 zurückgegeben. Das hat den Vorteil, dass mit Formularen sehr bequem gerechnet werden kann, da hier oft multipliziert wird (etwa Stückzahl mal Preis). Ist ein Eingabewert eine leere Zeichenkette oder allgemein keine Zahl, so wird 0 zurückgegeben, und das Ergebnis der Multiplikation ist auch 0.

Die im Folgenden aufgeführte Funktion benutzt eine bereits zuvor entwickelte Funktion, so dass Sie auf eine konsistente Namensgebung achten müssen.

function smartEval(n) {
   var s = "" + n;  //Umwandlung in eine Zeichenkette
   for (var i=0; i<s.length; i++) { //Kommata in Zahlen
      if (s.charAt(i) == ",") {

         s = s.substring(0, i) + "." + s.substring(
            i+1, s.length);
      }
   }
   if (isAFrac(s)) {
      return 1*s;  //Umwandlung in einen Zahlenwert
   } else {
      return 0;
   }
}

9.8 Reguläre Ausdrücke 
topBilder

Die Programmiersprache Perl ist unter anderem deswegen so beliebt, weil die Verwendung von regulären Ausdrücken auf diese Weise hoffähig geworden ist. Ein regulärer Ausdruck ist – stark vereinfacht gesagt – eine Zeichenfolge, die ein Textmuster repräsentiert. Dieses Textmuster kann dann in einer längeren Zeichenkette gesucht werden. Ein umgangssprachlich formuliertes Textmuster ist etwa: »ein Wort, das mit J beginnt und zwei ›a‹s enthält«. Auf dieses Textmuster würden beispielsweise sowohl »Java« als auch »JavaScript« passen. Mit regulären Ausdrücken kann man beispielsweise Formulareingaben sehr schnell überprüfen. Einige der Funktionen aus dem vorangegangenen Abschnitt können auf diese Weise viel einfacher programmiert werden. Ein Wort zur Warnung aber gleich vorweg: Mit den Methoden des String-Objekts, beispielsweise indexOf() und substring() kann man praktisch alle Funktionalitäten nachbilden, die reguläre Ausdrücke anbieten. Dazu ist jedoch eine Menge Programmierarbeit nötig (es gibt in der Tat auch Programme, die reguläre Ausdrücke in Programmcode umwandeln). Seit längerem kann man sich die Extra-Arbeit sparen und reguläre Ausdrücke mit JavaScript verwenden.


9.8.1 Kurzeinführung  

Ein Muster kann eine einfache Zeichenfolge sein, beispielsweise "abc". Das Interessante an Mustern sind jedoch die Sonderzeichen und Ausdrücke, mit denen man Muster flexibler gestalten kann. Im Folgenden werden die wichtigsten dieser Sonderzeichen und Ausdrücke vorgestellt.

Sonderzeichen zum Zählen

Mit diesen Ausdrücken kann angegeben werden, wie oft ein gewisses Zeichen vorkommen darf oder muss. Der Ausdruck muss dabei immer direkt hinter dem Zeichen stehen.

gpBilder
 
?: Nullmal oder einmal. Auf das Muster "ab?c" passen also "ac" und "abc", aber nicht "abbc".
gpBilder
 
*: Nullmal oder mehrmals. Auf das Muster "ab*c" passen also "ac", "abc" und "abbc", aber nicht "adc".
gpBilder
 
+: Einmal oder mehrmals. Auf das Muster "ab+c" passen also "abc" und "abbc", aber nicht "ac".
gpBilder
 
{n}: Genau n-mal. Auf das Muster "ab{1}c" passt also "abc", aber nicht "abbc". Das Muster ließe sich zu "abc" vereinfachen.
gpBilder
 
{n,m}: Zwischen n- und m-mal. Auf das Muster "ab{2,3}c" passen also "abbc" und "abbbc", aber nicht "abc".
gpBilder
 
{n,}: Mindestens n-mal. Das Muster "ab{1,}" ist also äquivalent zu "ab+c". Dieser Ausdruck kann auch vollständig durch einige der vorherigen Ausdrücke ersetzt werden; äquivalent zu "ab{5,}c" ist beispielsweise "ab{4}b+c".
gpBilder
 
{,m}: Höchstens n-mal. Das Muster "ab{1,}" ist also äquivalent zu "ab+c". Dieser Ausdruck kann auch vollständig durch einige der vorherigen Ausdrücke ersetzt werden; äquivalent zu "ab{5,}c" ist beispielsweise "ab{4}b+c".

Die Sonderzeichen zum Zählen müssen sich nicht nur auf ein einzelnes Zeichen beziehen. Wenn man mehrere Zeichen mit runden Klammern umgibt, werden diese als Gruppe behandelt (die Klammern sind hierbei Sonderzeichen, das Muster sucht also nicht nach den Zeichen "(" und ")"). Auf das Muster "(abc)+" passen also unter anderem "abc", "abcabc" und "abcabcabc".

Metazeichen

Innerhalb eines regulären Ausdrucks versteht man unter einem Metazeichen ein normales Zeichen, das durch einen vorangestellten Backslash () eine besondere Bedeutung erhält. JavaScript unterstützt die folgenden Metazeichen:

gpBilder
 
: Wortgrenze. An der Stelle, an der dieses Metazeichen steht, muss ein Wort beginnen oder aufhören, damit das Muster passt. Auf das Muster "abc" passt beispielsweise "abcde", aber nicht "ababc". Auf "abc" passt nur "abc"; das erste steht für den Wortanfang, das zweite für das Wortende.
gpBilder
 
B: Keine Wortgrenze, das Gegenteil von . Auf das Muster "Babc" passt beispielsweise "ababc", aber nicht "abcde". Auf das Muster "BabcB" passt weder "ababc" (Wortende) noch "abcde" (Wortanfang), aber "babcb".
gpBilder
 
d: Ziffer. Auf das Muster "dddd" passt beispielsweise "0815", aber nicht "R2D2".
gpBilder
 
D: Keine Ziffer. Auf das Muster "DD" passt weder "12" noch "A1" noch "1A", aber "AB".
gpBilder
 
s: Leerzeichen. Auf das Muster "JavasScript" passt "Java Script", aber nicht "Java-Script".
gpBilder
 
S: Kein Leerzeichen. Auf das Muster "JavaSScript" passen "Java_Script" und "Java-Script", aber nicht "Java Script".
gpBilder
 
w: Buchstabe, Ziffer oder Unterstrich (_). Auf das Muster "w" passen also beispielsweise "A", "a", "1" und "_", aber nicht "!".
gpBilder
 
W: Kein Buchstabe oder Ziffer oder Unterstrich. Auf das Muster "W" passt beispielsweise "!", aber nicht "A", "a" oder "_".

Weitere Ausdrücke

gpBilder
 
.: Jedes beliebige Zeichen außer einem Zeilensprung. Auf das Muster ".." passt also jede beliebige Folge zweier Zeichen.
gpBilder
 
[...]: Eine Auswahlliste von Zeichen. Aus der Liste von Zeichen kann genau eines ausgewählt werden. Die Zeichen stehen dabei hintereinander. Es können auch Abkürzungen vorgenommen werden. Beispielsweise bezeichnet "[A-Z]" alle Großbuchstaben und "[0 – 9]" alle Ziffern. Auf das Muster "[LJ]ava" passen also beispielsweise "Lava" und "Java", aber nicht "Cava". Das Metazeichen "w" kann durch folgendes Muster ausgedrückt werden: "[a-zA-Z0 – 9_]" (alle Klein- und Großbuchstaben, Ziffern und der Unterstrich).
gpBilder
 
[^...]: Negierte Auswahlliste. Das Muster "[^a-zA-Z0 – 9_]" ist also äquivalent zum Metazeichen "W".
gpBilder
 
^: Zeilenanfang. Auf das Muster "^Java" passen beispielsweise "Java" und "JavaScript", aber nicht "I love Java".
gpBilder
 
$: Zeilenende. Auf das Muster "Java$" passen beispielsweise "I love Java" und "I hate Java" und "Java", aber nicht "JavaScript".
gpBilder
 
: Entwertung eines Sonderzeichens. Das Dollar-Symbol "$" hat in regulären Ausdrücken eine besondere Bedeutung. Will man aber nach dem Währungssymbol suchen, so muss man "$" mit einem vorangestellten Sonderzeichen entwerten. Also passen auf das Muster "ddd $" beispielsweise "100 $" und "200 $" (und jeder andere dreistellige Dollarbetrag). Natürlich kann ein Backslash auch sich selbst entwerten; auf das Muster "" passt "".
gpBilder
 
|: Oder-Operation. Auf das Muster "abc|def" passen unter anderem "abc" und "def".


9.8.2 Ein Objekt erzeugen  

Ein regulärer Ausdruck kann mit JavaScript auf zwei Arten erstellt werden:

1. var re = new RegExp("ab+c", "gi"); Der erste Parameter ist der reguläre Ausdruck, was unschwer zu erkennen ist. Der zweite Parameter ist optional, kann aber – wenn er angegeben wird – einen der Werte "", "g", "i" oder "gi" haben (als regulärer Ausdruck geschrieben: "g?i?"). Hierbei steht "g" für global, und das ist beim Ersetzen wichtig. So kann ein Muster auch mehrmals in einer Zeichenkette gesucht (und gefunden) werden. Für »case insensitive«, also unabhängig von Groß- und Kleinschreibung, steht "i". Somit ist new RegExp("[a-zA-Z]") äquivalent zu new RegExp("[a-z]", "i").
       
2. var re = /ab+c/gi; Die Bedeutung des "gi" am Ende des Ausdrucks ist die gleiche wie im ersten Punkt. Der Unterschied ist hier, dass der reguläre Ausdruck von Schrägstrichen begrenzt wird. Das sieht im Vergleich zur restlichen JavaScript-Syntax etwas merkwürdig aus, weshalb ich persönlich die erste Variante bevorzuge. In diesem Buch wird jedoch die zweite Variante bevorzugt, weil die erste den Nachteil hat, dass z.  B. der Backslash innerhalb eines Strings entwertet werden muss, also etwa "d". Das macht den Code für den Einsteiger nicht gerade lesbarer. Bei der zweiten Variante ist außerdem zu beachten, dass der Schrägstrich dann ja eine besondere Bedeutung hat und innerhalb des Musters mit einem Backslash entwertet werden muss, damit der JavaScript-Interpreter das Ende des Musters findet. Das Muster "dd/dd" passt beispielsweise auf "08/15", und new RegExp("dd/dd") ist auch korrekt, aber mit der zweiten Schreibweise muss es /dd/dd/ heißen.
       


9.8.3 Mit dem Objekt arbeiten 
topBilder

Auf eine der beiden vorgestellten Arten können Sie ein RegExp-Objekt erzeugen. Dieses Objekt stellt mehrere Methoden zur Verfügung, die Ihnen bei der Anwendung von regulären Ausdrücken von Nutzen sein werden.

Einen Treffer suchen

Man spricht im Englischen von match, einem Treffer, wenn das Muster in der zu durchsuchenden Zeichenkette vorhanden ist. Die dazugehörige Methode lautet test("Zeichenkette"). Das folgende Codestück überprüft, ob es sich bei der Zeichenkette um eine deutsche Postleitzahl handeln könnte:

var re = /d{5}/; var plz1 = re.test("01234"); //gibt true zurück var plz2 = re.test("8000"); //gibt false zurück var plz2 = re.test("D8000"); //gibt false zurück

Mit dem folgenden Code wird überprüft, ob es sich bei dem Eingabewert um eine ganze Zahl handelt. Dieser Test wurde weiter oben schon etwas mühsam entwickelt; hier handelt es sich um deutlich weniger Schreibarbeit:

var re = /0|-?[1–9]d*/; var zahl1 = re.test("12345"); //gibt true zurück var zahl2 = re.test("01234"); //gibt false zurück

Zur Erklärung: Eine ganze Zahl ist entweder die Null oder eine Ziffernfolge, die nicht mit der Null beginnt (also eine Ziffer von 1 bis 9). Hinter der führenden Ziffer stehen beliebig viele weitere Ziffern oder aber auch keine Ziffer mehr. Die Ziffernfolge kann durch ein optionales Minus-Zeichen eingeleitet werden.

Treffer zurückliefern

In der Praxis kommt es oft nicht nur darauf an, einen Treffer zu finden, sondern auch darauf, den auf das Muster passenden Teil der Zeichenkette zu erhalten, und teilweise auch darauf, Teile davon zu erhalten.

Oben wurde bereits erwähnt, dass man mit runden Klammern einzelne Zeichen im Muster gruppieren kann. Das ist nicht nur für Nummerierungen von Bedeutung, sondern auch für das Ergebnis, da man den Wert der einzelnen Klammern gezielt abfragen kann. Dies soll an einem Beispiel verdeutlicht werden.

In einem Firmen-Intranet wird die Stundenerfassung elektronisch erledigt. Um Eingabefehler abzufangen, soll JavaScript verwendet werden. Eine Stundenangabe soll im Format »0:30« bzw. »12:45« erfolgen. Der folgende reguläre Ausdruck prüft auf dieses Format. Beachten Sie, dass dabei die ersten runden Klammern um die Stundenzahl stehen und die zweiten runden Klammern um die Minutenzahl.

var dauer = /(1?d):([0–5]d)/g;

Der gewerkschaftlich organisierte Leser wird gewiss verzeihen, dass Arbeitszeiten bis zu 19 Stunden und 59 Minuten zugelassen werden; der Leser mit Personalverantwortung möge übersehen, dass keine Arbeitszeiten über 20 Stunden erlaubt sind.

In diesem Beispiel wird die Methode exec("Zeichenkette") des RegExp-Objekts verwendet. Im Gegensatz zu test("Zeichenkette") wird hier nicht einfach ein boolescher Wert zurückgegeben, sondern ein spezielles Objekt, das den Teil der Zeichenkette enthält, auf den das Muster passt. Außerdem werden auch die Teile innerhalb der runden Klammern abgespeichert. Folgender Code wird das verdeutlichen:

var dauer = /(1?d):([0–5]d)/; var test = dauer.exec("abc2:45def"); document.write("Uhrzeit: " + test[0] + "<br />"); //2:45 document.write("Stunden: " + test[1] + "<br />"); //2 document.write("Minuten: " + test[2] + "<br />"); //45

Das Objekt, das exec() zurückgibt, ist so etwas wie ein Array. Im Element mit dem Index 0 steht der Teil der Zeichenkette, auf den das Muster passt. Im Element mit dem Index 1 steht der Teil, der im Muster durch die ersten Klammern umgeben ist.

Mehrere Treffer

Oft ist es nicht nur von Bedeutung herauszufinden, ob in einer Zeichenkette ein Muster vorkommt und wie dieses eine Vorkommen aussieht, sondern auch, wie oft es vorkommt und wie diese Vorkommen denn aussehen. Für diese Zwecke ist die Methode match() des String-Objekts geeignet. Als Parameter kann dazu ein regulärer Ausdruck übergeben werden. Die Methode match() gibt alle Treffer in einem Array zurück. Sobald man dieses Array hat, kann man auf die herkömmliche Art und Weise die Ergebnisse auslesen.

An folgendem Beispiel soll dies gleich einmal verdeutlicht werden. Innerhalb einer Zeichenkette sollen alle Wörter herausgesucht werden, die den Buchstaben "e" nur einmal enthalten. Der zugehörige reguläre Ausdruck lautet "[a-df-zA-DF-Z]*e[a-df-zA-DF-Z]*". Er sieht kompliziert aus, bedeutet aber einfach, dass es sich um ein Wort handelt (angezeigt durch die Wortgrenzen am Anfang und am Ende), das ein e enthält und ansonsten aus weiteren Buchstaben ungleich e (also von a bis d und von f bis z) besteht. Des Weiteren muss die Option "g" für globales Suchen angegeben werden, damit auch alle Vorkommen gefunden werden.

var nur_ein_e = /[a-df-zA-DF-Z]*e[a-df-zA-DF-Z]*/g; var satz = "Die Geister, die ich rief, ward ich nicht mehr los"; var liste = satz.match(nur_ein_e); if (liste) { document.write(liste.length + " Treffer gefunden!<br />"); for (var i=0; i<liste.length; i++) { document.write(liste[i] + "<br />"); } } else { document.write("Keine Treffer!"); }

Bilder

Abbildung 9.10     Alle Wörter, die nur ein »e« enthalten

Suchen und ersetzen

Die letzte Anwendung für reguläre Ausdrücke, die hier vorgestellt werden soll, wird mit am häufigsten verwendet – wurde aber lange Zeit nur vom Netscape Navigator unterstützt. Man kann nicht nur nach Mustern suchen, sondern diese Muster auch durch andere Muster ersetzen. Ein Beispiel war die Umformung einer »deutschen« Dezimalzahl in die amerikanische Schreibweise. Dazu mussten alle Kommata durch Dezimalpunkte ersetzt werden. Der folgende Code erledigt das sehr einfach:

var komma = /,/g; var dezimalzahl_brd = "3,1415965"; var dezimalzahl_usa = dezimalzahl_brd.replace(komma, "."); document.write(dezimalzahl_usa); //3.1415965

Auch hier gibt es fortgeschrittenere Anwendungen. Ein weiterer Unterschied in der Schreibweise zwischen Deutschland und den USA besteht beim Datum. Während das Format hierzulande meistens tt.mm.jj bzw. tt.mm.jjjj ist (t = Tag, m = Monat, j = Jahr), so verwendet man in den Vereinigten Staaten in der Regel jj-mm-tt bzw. jjjj-mm-tt. Die Aufgabe besteht jetzt darin, deutsche Datumsangaben durch das amerikanische Pendant zu ersetzen. Beginnen wir mit dem regulären Ausdruck für einen Datumswert. Das Jahr ist zwei- oder vierstellig, Monat und Tag sind aber unter Umständen einstellig. Außerdem dürfen als Werte für den Tag nur Werte von 1 bis 31, beim Monat nur Werte von 1 bis 12 verwendet werden (unmögliche Daten wie etwa der 30. Februar werden hier übergangen, um das Beispiel einfach zu halten). Der Ausdruck sieht also recht kompliziert aus, aber die Idee, die dahinter steht, ist recht einfach. Probieren Sie es doch einmal mit indexOf()!

var datum = /(0?[1–9]|[12][0–9]|3[01]).(0?[1–9]|1[0–2]) .(d?d?dd)/;

Wie Sie sehen, wurden um Tag, Monat und Jahr runde Klammern eingefügt. Der Sinn des Ganzen zeigt sich bei der Übersetzung in das amerikanische Format. Die Jahreszahl muss nach vorn, der Tag nach hinten. Während beim Ergebnis der Methode exec() die Inhalte der Klammern in ergebnisarray[1], ergebnisarray[2] und so weiter stehen, findet man die Inhalte der Klammern bei regulären Ausdrücken in den speziellen Variablen $1, $2 und so weiter. Mit folgendem Code wandelt man ein deutsches Datum in das amerikanische Format um:

function deutsch_zu_usa(d) { var datum = /(0?[1–9]|[12][0–9]|3[01]).(0? [1–9]|1[0–2]).(d?d?dd)/; if (datum.test(d)) { return d.replace(datum, "$3-$2-$1"); } else { return d; } } document.write(deutsch_zu_usa("Das dritte Jahrtausend begann am 1.1.01 – und keinen Tag früher"));

Mit d.replace(datum, "$3-$2-$1") wird der reguläre Ausdruck auf die Eingabe angewandt. In der dritten Klammer, also in $3, steht die Jahreszahl, in $2 der Monat und in $1 der Tag. Die Ausgabe lautet dann: "Das dritte Jahrtausend begann am 01 – 1 – 1 – und keinen Tag früher". In der Variablen $0 steht übrigens analog zu exec() der gesamte Treffer, in diesem Fall also "1.1.2001".

Die Funktion replace() gibt es auch in einer Luxusvariante. Wie beim Netscape-Pendant auch werden an replace() zwei Parameter übergeben. Der erste ist ein regulärer Ausdruck, und der zweite ist eine Funktion, die vom JavaScript-Interpreter automatisch beim Aufruf von replace() aufgerufen wird. An diese Funktion werden (vom JavaScript-Interpreter!) die folgenden Parameter übergeben (in der angegebenen Reihenfolge):

gpBilder
 
die Zeichenkette, die auf das Muster passt
gpBilder
 
der Reihe nach alle Untermuster: Wenn im regulären Ausdruck Klammern verwendet worden sind, werden die Entsprechungen in diesen Klammern (also die Werte von $1, $2 usw.) an die Funktion übergeben.
gpBilder
 
die Position, an der das Muster in der zu überprüfenden Zeichenkette gefunden wurde
gpBilder
 
die überprüfte Zeichenkette

Die Funktion, die von replace() aufgerufen wird, muss nun nur noch Folgendes leisten: einen Wert zurückliefern. Durch diesen Wert wird das gefundene Muster in der zu überprüfenden Zeichenkette ersetzt.

Das Beispiel von oben – die Umformung des Datums in ein amerikanisches Format – kann folgendermaßen angepasst werden:

function deutsch_zu_usa(d) { var datum = /(0?[1–9]|[12][0–9]|3[01]).(0? [1–9]|1[0–2]).(d?d?dd)/; if (datum.test(d)) { return d.replace(datum, d_zu_usa); } else { return d; } } function d_zu_usa(str, m1, m2, m3, pos, quelle) { return m3 + "-" + m2 + "-" + m1; //entspricht $3-$2-$1 } document.write(deutsch_zu_usa("Das dritte Jahrtausend begann am 1.1.01 – und keinen Tag früher"));

Microsoft hat die replace()-Funktion erst in Version 5.5 des Internet Explorers eingebaut. Mit früheren Versionen, inklusive Version 5, funktioniert das noch nicht – zum Glück sind mittlerweile beide alten Versionen so gut wie ausgestorben.

Neues in JavaScript 1.5

Ab JavaScript 1.5, das zurzeit nur von den aktuellen Mozilla-Derivaten unterstützt wird, erhält der Fragezeichenoperator ? eine weitere Bedeutung. Normalerweise arbeitet die Mustererkennung bei einem regulären Ausdruck »gierig«; das heißt, es wird ein möglichst großer Treffer zurückgeliefert. Hier ein Beispiel:

var htmltag = /(<.*>)/; var test = htmltag.exec("<p>JavaScript</p>"); test[0] = test[0].replace("<", "&lt;"); test[0] = test[0].replace(">", "&gt;"); document.write("HTML-Tag: " + test[0]);

Was gibt dieses Programm aus? Vielleicht erwarten Sie als Ausgabe "<p>", denn das würde auf das angegebene Muster passen. Doch diese Annahme ist leider falsch, denn es wird wie gesagt immer ein möglichst großer Treffer zurückgeliefert, in diesem Falle also "<p>JavaScript</p>". Wenn Sie einen möglichst kleinen Treffer erzielen möchten, müssen Sie direkt nach einem Sonderzeichen zum Zählen (*, +, ?, {, }) den Fragezeichenoperator angeben. Folgender Code würde also in der Tat "<p>" ausgeben:

var htmltag = /(<.*?>)/; var test = htmltag.exec("<p>JavaScript</p>"); test[0] = test[0].replace("<", "&lt;"); test[0] = test[0].replace(">", "&gt;"); document.write("HTML-Tag: " + test[0]);

Wenn Sie diesen Code mit dem Internet Explorer 5.0x oder einer älteren Version ausführen (oder einem Anno-Tobak-Netscape), erhalten Sie entweder ein falsches Ergebnis oder eine Fehlermeldung. Der Internet Explorer 5.5 und auch die Nachfolgerversionen 6 und 7 unterstützen zwar kein JavaScript 1.5, haben aber diese Erweiterung der Syntax für reguläre Ausdrücke auch im Programm.

Bilder

Abbildung 9.11     Aktuelle Browser interpretieren den regulären Ausdruck richtig.

 

Das Tutorial ist ein Auszug aus der 7. Auflage des Buches von Christian Wenz:

JavaScript und Ajax - Das umfassende Handbuch
Galileo Computing, 853 Seiten, 8. aktualisierte Auflage

Die Veröffentlichung des Kapitels erfolgt mit freundlicher Genehmigung von Galileo Press.

Mehr Informationen zum aktualisierten Fachbuch für Webentwickler gibt es hier: JavaScript und Ajax
 
Alle Kapitel des Buches:
1 Grundlagen
2 JavaScript-Praxis
3 Professionelles JavaScript
4 Web 2.0 und Ajax
5 Kommunikation
6 Serverseitige Programmierung
7 Sicherheit
8 Praxis
9 Anhang
Bilder


 

DVD-Werbung
Kommentare
Achtung: Du kannst den Inhalt erst nach dem Login kommentieren.
Portrait von Pictureboy28
Portrait von marcmaroc
  • 13.09.2009 - 19:56

Danke! Hat mir vieles erleichtert beim programmieren einer dynamischen Datenabfrage.

Portrait von Kampfrolli
  • 17.05.2008 - 20:15

Sehr schönes tut hat mir bei einigen sehr geholfen danke dafür

Portrait von skyflight1981
  • 14.04.2008 - 01:28

Nochmals Danke für die Teile!

Portrait von Nerosmeel
  • 13.04.2008 - 16:32

spitze :-) würd mich glat reizen das ganze buch zu kaufen.

Portrait von Maulwurf16
  • 12.04.2008 - 09:34

gefällt mir sehr gut. Gerade eben habe ich im Internet nach dynamischen Menüs geschaut und dann seh ich dieses tutorial.

x
×
×