JavaScript/Tutorials/Wertübergabe zwischen verschiedenen HTML-Dokumenten

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Der folgende Artikel ist lediglich eine Zusammenführung und Überarbeitung zweier älterer Artikel, die ihrerseits Hatto von Hatzfeld und Stefan Puff verfasst haben. Dank geht auch an Torsten Anacker, dessen Code zur Zerlegung des Querystrings Pate gestanden hat, sowie Thomas Fischer, der einst Anregungen zur Verwendung von Window.name gab.

Problemstellung

Oft möchten Javascript-Programmierer Variablen, die sie in einem Dokument definiert haben, auch in einem anderen Dokument, das anschließend im selben Browserfenster geladen wird, verfügbar haben. Üblicherweise werden für diesen Zweck Cookies verwendet. Doch nicht immer haben Nutzer Cookies zugelassen oder wollen sie nur nach Einzelbestätigung akzeptieren.

Wertübergabe mittels URL

Ein weiterer Lösungsansatz nutzt die Adresse des empfangenden Dokuments, um die gewünschten Daten als Abfrageparameter zu übergeben. Dabei wird der URL ein sogenannter Query String angehängt, welcher nach einem Fragezeichen die Daten in einer kodierten Form enthält. Ein Query String mit mehreren Parameter/Wert-Paaren ist nach dem Schema ?name1=wert1&name2=wert2&name3=wert3&... aufgebaut. Dieser Mechanismus wird unter anderem bei der Übergabe von Formulardaten über die HTTP-Methode GET genutzt. Im empfangenden Dokument können die URL-Parameter über die JavaScript-Objekteigenschaft location.search ausgelesen, getrennt und dekodiert werden.

Ein großer Nachteil dieser Variante ist allerdings, dass das empfangende Dokument neu vom Server angefordert werden muss, selbst wenn es bereits im Cache (Zwischenspeicher) des Browsers oder eines Proxy-Servers liegen. Dies führt auch dazu, dass der Offline-Modus der meisten Browser im Zusammenhang mit der Wertübergabe über die URL nicht funktioniert.

Um die Daten über die URL übergeben zu können, müssen sie vorher URL-kodiert werden, das heißt für die Übertragung vorbereitet werden. Dabei lässt sich die vordefinierte Methode escape() verwenden, mit welcher Sonder- und Steuerzeichen in Parameternamen und Wertstrings maskiert werden können.

Während es bei der zuvor beschriebenen Framelösung möglich ist, alle möglichen Objekte bzw. Variablen gleich welchen Typs auf einfache Weise weiterzugeben, sind bei der Übergabe mittels URL letztlich nur String-Variablen möglich. Bei Variablen des Types Boolean und Number ist dies unproblematisch, da JavaScript sie automatisch in String-Variablen umwandeln kann, ohne dass Informationen verloren gehen. Bei komplexeren Datenstrukturen wie Arrays oder eigenen Objekten hingegen sind aufwendigere Techniken notwendig, um die Array-Elemente bzw. Objekteigenschaften jeweils in Parameter/Wert-Paare umzuwandeln. Im Folgenden wird ein Weg zur Übergabe von einfachen Variablen beschrieben.

Weg zur Übergabe von einfachen Variablen
<!DOCTYPE HTML> <html> <head> <title>Wertübergabe mittels URL - Sendendes Dokument</title> </head> <body> <form method="GET" action="querystring-empfangen.htm"> <p>Wie heißen Sie?</p> <p> <label for="vorname">Vorname:</label> <input type="text" name="vorname" id="vorname" size="25"> </p> <p> <label for="nachname">Nachname:</label> <input type="text" name="nachname" id="nachname" size="25"> </p> <p><input type="submit" value="Senden"></p> </form> </body> </html>


Das Eingabeformular kennen wir bereits. Geändert hat sich das action-Attribut, in dem die URL des empfangenden Dokuments angegeben wird. Auch wenn GET die Standard-Methode zum Übertragen von Formulardaten ist, geben wir zur Verdeutlichung zusätzlich method="GET" an.


Beispiel – empfangendes Dokument
<!DOCTYPE HTML> <html> <head> <title>Wertübergabe mittels URL - Empfangendes Dokument</title> <script type="text/javascript"> function Werteliste (querystring) { if (querystring == '') return; var wertestring = querystring.slice(1); var paare = wertestring.split("&"); var paar, name, wert; for (var i = 0; i < paare.length; i++) { paar = paare[i].split("="); name = paar[0]; wert = paar[1]; name = unescape(name).replace("+", " "); wert = unescape(wert).replace("+", " "); this[name] = wert; } } var liste = new Werteliste(location.search); </script> </head> <body> <h1>Übergebene Daten</h1> <table border="1" cellpadding="5" cellspacing="0"> <tr> <th>Feldname</th> <td>Eintrag</td> </tr> <script type="text/javascript"> for (var eigenschaft in liste) { document.write( "<tr><td>" + eigenschaft + "</td>" + "<td><code>" + liste[eigenschaft] + "</code></td></tr>" ); } </script> </table> </body> </html>

Zuerst werden die Formulardaten mittels location.search aus der URL extrahiert und der Funktion Werteliste() übergeben. Dessen Aufruf mit dem new-Operator erzeugt ein eigenes Objekt. Werteliste tritt als Konstruktor-Funktion auf und gibt die erzeugte Objekt-Instanz zurück, die in der Variable liste gespeichert wird.

In der Funktion Werteliste() werden zuerst die Datenpaare, bestehend aus Parametername und Wert, und anschließend jedes Wertepaar getrennt. Die Trennung der Datenpaare erfolgt mithilfe der split-Methode. Sie gibt einen Array zurück, der anschließend mit einer for-Schleife durchlaufen wird. Jedes Element dieses Array steht für ein Datenpaar-String nach dem Schema name=wert. Mit erneuter Anwendung von split() wird der String in Name und Wert getrennt, die in den Variablen name bzw. wert zwischengespeichert werden.

Danach folgt die Decodierung mit der Methode unescape(). Zusätzlich wird das Zeichen + mittels replace() durch ein Leerzeichen ersetzt, denn unescape() lässt diese notwendige Decodierung aus.

Die ausgelesenen und dekodierten Werte werden schließlich mit this[name] = wert als Eigenschaften am neu erzeugten Objekt gespeichert.

Das Ergebnis der Decodierung steht in der Variable liste in Form eines Objekts mit mehreren Eigenschaften zur Verfügung. (Sollten keine Werte übergeben werden, z. B. wenn die Seite direkt aufgerufen wird, hat das Objekt einfach keine Eigenschaften.) Das liste-Objekt verhält sich wie ein assoziativer Array: Wenn einen Datenpaar mit dem Namen »vorname« übergeben wurde, können wir dessen Wert mit der Schreibweise liste.vorname auslesen. Wenn der Name Leerzeichen oder Sonderzeichen enthält, z. B. »Name des Haustiers«, müssen wir uns der Alternativ-Schreibweise zum Zugriff auf Objekteigenschaften bedienen: liste["Name des Haustiers"].

Im Dokumentkörper des Beispiels wird mittels JavaScript eine Tabelle generiert, in der die Daten aus dem liste-Objekt ausgegeben werden. Ein solches Objekt muss mit einer for-in-Schleife durchlaufen werden.

Lösungsansatz mit Window.name

Das Window-Objekt kennt die Eigenschaft name. Sie bleibt über die ganze »Lebensdauer« des Browserfensters erhalten und kann durch eine Javascript-Zuweisung geändert werden. So bietet es sich an, diese Eigenschaft zur Sicherung von Daten zu nutzen, die einen Wechsel des Fensterinhaltes überstehen sollen.

sendendes Dokument
<!doctype html> <html> <head> <title>Wertübergabe mittels window.name - Sendendes Dokument</title> </head> <body> <script type="text/javascript" src="storage.js"></script> <script type="text/javascript"> function sichern () { storage.set("vorname", document.forms.formular.elements.vorname.value); storage.set("nachname", document.forms.formular.elements.nachname.value); location.href = "window-name-empfaenger.html"; } </script> <form name="formular" action="" onsubmit="sichern(); return false;"> <p>Wie heißen Sie?</p> <p> <label for="vorname">Vorname:</label> <input type="text" name="vorname" id="vorname" size="25"> </p> <p> <label for="nachname">Nachname:</label> <input type="text" name="nachname" id="nachname" size="25"> </p> <p><input type="submit" value="Senden"></p> </form> </body> </html>
sendendes Dokument
Quelltext von storage.js var storage = new function () { /* --------- Private Properties --------- */ var dataContainer = {}; /* --------- Private Methods --------- */ function linearize () { var string = "", name, value; for (name in dataContainer) { value = encodeURIComponent(dataContainer[name]); name = encodeURIComponent(name); string += name + "=" + value + "&"; } if (string != "") { string = string.substring(0, string.length - 1); } return string; } function read () { if (window.name == '' || window.name.indexOf("=") == -1) { return; } var pairs = window.name.split("&"); var pair, name, value; for (var i = 0; i < pairs.length; i++) { if (pairs[i] == "") { continue; } pair = pairs[i].split("="); name = decodeURIComponent(pair[0]); value = decodeURIComponent(pair[1]); dataContainer[name] = value; } } function write () { window.name = linearize(); } /* --------- Public Methods --------- */ this.set = function (name, value) { dataContainer[name] = value; write(); }; this.get = function (name) { var returnValue = dataContainer[name]; return returnValue; }; this.getAll = function () { return dataContainer; }; this.remove = function (name) { if (typeof(dataContainer[name]) != undefined) { delete dataContainer[name]; } write(); }; this.removeAll = function () { dataContainer = {}; write(); }; /* --------- Construction --------- */ read(); };
empfangendes Dokument
<!doctype html"> <html> <head> <title>Wertübergabe mittels window.name - Empfangendes Dokument</title> <script src="storage.js"></script> <script> var liste = storage.getAll(); </script> </head> <body> <h1>Übergebene Daten</h1> <table border="1" cellpadding="5" cellspacing="0"> <tr> <th>Feldname</th> <th>Eintrag</th> </tr> <script type="text/javascript"> for (var eigenschaft in liste) { document.write( "<tr><td>" + eigenschaft + "</td>" + "<td><code>" + liste[eigenschaft] + "</code></td></tr>" ); } </script> </table> </body> </html>

Um das Schreiben und Auslesen von Daten über window.name zu erleichtern, wird ein eigenes Objekt namens storage eingeführt. Dessen Definition finden wir in in der externen JavaScript-Datei storage.js, die im sendenden und im empfangenden Dokument eingebunden wird (siehe JavaScript in separaten Dateien). Die Nutzung dieses Fertigscriptes ist denkbar einfach – zum Verständnis der internen Umsetzung ist jedoch grundlegendes Wissen über objektorientierte Programmierung notwendig. Wenn Sie die folgenden Erklärungen nicht auf Anhieb verstehen, so ist das nicht schlimm.

Das eigene Objekt storage wird new function () { ... } definiert. Das bedeutet, es wird eine Instanz der folgenden Funktion erzeugt. Lassen Sie sich nicht verwirren, dies ist nichts anderes als das Instantiieren von Objekten nach dem bekannten Schema var instanz = new Konstruktor(). Das Besondere hier ist lediglich, dass die Konstruktor-Funktion anonym notiert wurde, also keinen Namen hat. Wir könnten ebenso schreiben:

Beispiel
<!doctype html"> function storageConstructor () { /* ...*/ } var storage = new storageConstructor();

Allerdings wird durch die Kurzschreibweise mit der anonymen Funktion die überflüssige globale Variable storageConstructor vermieden.

In dieser anonymen Konstruktor-Funktion befindet sich jedenfalls die gesamte Logik:

  • Es gibt eine private Eigenschaft namens dataContainer. Dies Object in welchem alle Namen/Werte-Paare gespeichert werden.
  • Es gibt drei private, nur für die interne Verarbeitung benötigte Methoden.
    • linearize() erzeugt aus den Daten im dataContainer einen String, der dann in window.name gespeichert werden kann. Als Codierungsformat wird die bekannte URL-Codierung verwendet (name1=wert1&name2=wert2&...).
    • read() liest window.name aus, dekodiert die darin gespeicherten Daten und legt sie als JavaScript-Objekte im dataContainer ab.
    • write() ruft die Linearisierungsfunktion auf und schreibt deren Rückgabewert in window.name.
  • Es gibt fünf öffentliche Methoden, die über storage.methodenName() aufgerufen werden können:
    • set(name, value) speichert einen Wert unter einem bestimmten Namen. Erwartet dazu den Namen und den Wert als Parameter.
    • get(name) liefert den gespeicherten Wert mit dem angegebenen Namen zurück. Erwartet dazu den Namen als Parameter.
    • getAll() liefert alle gespeicherten Werte als Object zurück. Erwartet keine Parameter.
    • remove(name) entfernt den gespeicherten Wert mit dem angegebenen Namen. Erwartet dazu den Namen als Parameter.
    • removeAll() entfernt alle gespeicherten Werte. Erwartet keine Parameter.

Schließlich wird in der Konstruktor-Funktion einmalig die private Methode read() aufgerufen, um beim Laden des Dokuments die in window.name gespeicherten Daten auszulesen und sie später bereitstellen zu können.

Im sendenden Dokument befindet sich das bekannte Namens-Formular. Beim Absenden (onbsubmit) wird eine Funktion aufgerufen, die die Formulardaten mittels storage in window.name speichert. Dazu wird einfach die set-Methode aufgerufen und ihr wird der Wert des Formularfeldes übergeben:

Beispiel
<!doctype html"> storage.set("vorname", document.forms.formular.elements.vorname.value); storage.set("nachname", document.forms.formular.elements.nachname.value);

Danach wird mit location.href zur empfangenden Seite weitergeleitet. Diese liest mittels storage.getAll() alle gespeicherten Werte aus. Im Dokumentkörper wird das zurückgelieferte Objekt durchlaufen und deren Name/Wert-Paare ausgegeben – wir kennen auch dies bereits aus dem vorigen Lösungsansatz. Möchten wir nur einen einzigen gespeicherten Wert auslesen, so nutzen wir get():

var vorname = storage.get("vorname");

Mit dem vorgestellten storage-Objekt lassen sich komfortabel einfache Daten zwischen verschiedenen, nacheinander geladenen Dokumenten austauschen. Eine rigide Größenbeschränkung von window.name scheint nicht zu existieren, selbst eine Zeichenkette von mehreren 100 Kilobyte wird erfolgreich verarbeitet. Es dürfte jedoch geraten sein, in der regelmäßigen Verwendung nicht über einige Kilobyte hinauszugehen. Sie sollten ebenfalls beachten, dass der Zugriff auf das Fenster über dessen Namen durch den »Missbrauch« von window.name erschwert, wenn nicht unmöglich werden.

Anwendungsgebiete und Risiken

Mittlerweile gibt es Helferscripte, die das Lesen und Setzen von Cookies über document.cookie vereinfachen. Trotzdem bleibt immer das Risiko, dass der Browser bzw. der Anwender Cookies ablehnt oder sie durch einen Proxy schon im Vorhinein ausgefiltert werden. Der Lösungsansatz mit window.name kann in dem Fall eine Alternative sein.

Ob document.cookie oder window.name: Clientseitige Lösungen haben ähnliche Nachteile und sind nur in bestimmten Fällen geeignet. Die meisten dynamischen Webanwendungen setzen auf sogenannte Sessions (Sitzungen), eine serverseitige Lösung zum vorübergehenden Speichern von Daten. Clientseitig wird dann nur die Session-ID, die eindeutige Kennung der Sitzung gespeichert –- üblicherweise in einem Cookie, der aber nicht mit JavaScript, sondern ganz einfach HTTP gesetzt wird. Eine Beispielumsetzung in PHP stellt der Artikel Sessionbasiertes Loginsystem vor. Für serverseitig dynamische Webseiten wie etwa Foren, Communities, Online-Shops ist diese Lösung zuverlässiger und sicherer als eine eventuelle JavaScript-Lösung.

JavaScript-Wertübergabe ist also kein Ersatz für Sessions, sondern deckt andere Problemstellungen ab. Wenn Sie sowieso JavaScript voraussetzen können oder optionale Zusatz-Features mit JavaScript entwickeln, dann ist window.name ideal, um kleinere Einstellungen zu speichern. Ein Beispiel ist ein Script, dass die individuelle Anpassung der Schriftgröße erlaubt.

Wichtiger Sicherheitshinweis: Cookies können nur von der Webseite ausgelesen werden können, die sie auch gesetzt hat. Dieses grundlegende und entscheidend wichtige Sicherheitskonzept nennt sich Same-Origin-Policy. Demgegenüber müssen Sie sich unbedingt im Klaren darüber sein, dass window.name auch für fremde Webseite lesbar ist, die später im selben Fenster angezeigt werden. Für window.name gilt keine Same Origin Policy! Deshalb sollten Sie darin niemals vertrauliche Daten speichern, ansonsten ist im schlimmsten Fall Session Hijacking und Identitätsdiebstahl Tür und Tor geöffnet.

Ausblick auf Super-Cookies und die Übergabe komplexer Daten

Neben document.cookie und window.name gibt es in HTML5 mit localStorage eine weitere Technik, die komfortable Methoden zum Speichern und Lesen von Werten bereits mitbringt. Mit diesem DOM Storage wird das clientseitige Speichern damit denkbar einfach:

DOM Storage
sessionStorage.vorname = "Stefan"; // Nur für die Browser-Session speichern globalStorage['selfhtml.org'].vorname = "Stefan"; // für unbestimmte Zeit speichern

Schließlich sei eine Alternative zur Linearisierung von JavaScript-Daten nach dem Schema ?name1=wert1&name2=wert2&name3=wert3&... genannt. Wie gesagt unterstützen die vorgestellten Funktionen nur die Übermittlung von einfachen JavaScript-Werten, das heißt vor allem String-, Number- und Boolean-Werte. Komplexe Datentypen wie Arrays, Objects und reguläre Ausdrücke werden nicht korrekt kodiert und werden bei der Übertragung verstümmelt. Eine bessere Lösung wäre die Nutzung des Formates JSON und den Funktionen JSON.stringify() und JSON.parse(). Das vorgestellte Script storage.js kann einfach abgeändert werden, sodass die Daten mit JSON kodiert und dekodiert werden.