[Javascript & PHP] - Seiteninhalt aktualisieren ohne Reload
- Grundgedanke:
Den grössten Anteil an Daten verschlingt in Webseiten für gewöhnlich das Design.
Aufwendige Tabellenkonstrukte zum Seitenaufbau, Stylesheets, Grafiken, Navigation, Multimediaobjekte und Javascripte. All dies ist oft statischer Inhalt, welcher sich nie ändert.
Um jedoch neue Daten vom Server zu holen, muss in der Regel dieses gesamte Beiwerk neu geladen werden. Die einzige Möglichkeit, dies zu umgehen, ist die Verwendung von Frames oder Iframes, in welche HTML-Dokumente geladen werden. Wenn man darauf jedoch verzichten möchte, bietet Javascript weitere Möglichkeiten, wovon ich ich hier eine vorstellen möchte.
Das Prinzip ist recht einfach: Anstatt eine komplettes HTML-Dokument an den Browser zu Senden, werden diesem lediglich Javascript-Anweisungen übermittelt, mit welchen das aktuelle Dokument manipuliert wird. Ein "Reload" wird also nicht tatsächlich umgangen, er erfolgt jedoch im Hintergrund, ohne die aktuelle Seite komplett neu aufbauen zu Müssen.
- Vorgehensweise:
Besteht die Frage, wie man die neuen Daten in das aktuelle Fenster bekommt, ohne das Dokument neu zu Laden?
Folgende Möglichkeiten bestehen:
- Ändern des src-Attributes eines eingebundenen Javascriptes in die Adresse eines PHP-Skriptes, welches den nötigen Javascript-Code ausliefert(diese Methode funktioniert nach meiner Kenntnis nur im Internet-Explorer)
- Einhängen eines neuen <script>-Knotens in das Dokument (ebenfalls mit dem entsprechenden PHP-Skript als Ziel, einen Thread, welcher sich damit befasst, findet ihr bei Interesse im Javascript-Board)
- Senden einer Serveranfrage per XMLHttpRequest und Verarbeiten der Antwort (derzeit in Internet-Explorer ab Version 5 und aktuellen Versionen von Mozilla, Opera, Konqueror und Safari möglich)
- Laden eines den Javascript-Code enthaltenden HTML-Dokuments in ein verstecktes Frame oder iFrame
Ich werde die letztgenannte Methoden verwenden, da mit ihr die meisten Browser bedient werden können.
Im Detail sind dies DOM-Browser, getestet habe ich mit IE5+, Opera 7, Gecko-Browser(Mozilla, Netscape6+) und Konqueror. (Rein theoretisch liesse sich dies auch in Netscape4 und IE4 bewerkstelligen, darauf werde ich hier aber nicht weiter eingehen)
Um andere Browser von der Seitenbenutzung nicht auszuschliessen, wird das Dokument erst mittels Javascript den Bedürfnissen entsprechend vorbereitet.
- Vorbereitung:
Damit die Sache funktioniert, muss das HTML-Dokument 2 Vorraussetzungen erfüllen:
- Es muss ein (I)Frame enthalten, in welches die Datei geladen wird, welche das Dokument manipulieren soll. Dieses wird beim Laden per write() in das Dokument geschrieben
Javascript-Code:Code:if( document.getElementsByTagName && document.getElementsByTagName('body')[0].innerHTML ) { document.write( '<iframe name="loader"src="about:blank"style="display:none"></iframe>' ); }
Dabei ermittle ich eingangs, ob der Browser DOM-fähig ist. Weiterhin frage ich ab, ob er die Eigenschaft "innerHTML" kennt. Diese gehört nicht zum DOM-Sprachumfang, wird aber von den genannten Testbrowsern interpretiert.
Grund dafür, dass ich auf diese Methode zurückgreife, ist das aufwendige Verfahren zum Einfügen/Ersetzen von Knoten nach DOM-Methode. Dies würde einen der gewollten Effekte - das Reduzieren des Datenverkehrs - zunichte machen.
- Die Elemente des Dokumentes, welche das Aufrufen von Seiten ermöglichen (<form>, <a>, <area>), müssen dieses <iframe> als Zielfenster zugewiesen bekommen.
Javascript-Code:Code:for( i = 0; i < document.getElementsByTagName('*').length; ++i ) { objElem = document.getElementsByTagName('*')[i]; if( objElem.tagName.match(/^(A|FORM|AREA)$/i) && objElem.target == '_self' ) { objElem.target = 'loader'; if(objElem.tagName.match(/^FORM$/i)) { objField=document.createElement( 'input' ); objField.setAttribute( 'type', 'hidden' ); objField.setAttribute( 'name', 'js' ); objField.setAttribute( 'value', '1' ); objElem.appendChild( objField ); } else { objElem.setAttribute( 'href', (objElem.getAttribute('href').indexOf('?') > -1 ) ? objElem.getAttribute('href') + '&js=1' : objElem.getAttribute('href') + '?js=1' ); } } }
Dabei werden mit
Javascript-Code:Code:for(i=0;i< document.getElementsByTagName('*').length; ++i)
Wurde eines der gesuchten Elemente gefunden, wird dessen target-Attribut geprüft, ob es den Wert "_self" enthält(diese Prüfung nehme ich vor, um flexibel in der Manipulation der Elemente zu bleiben).
Im Fall eines Treffers wird bei <a> und <area> das Linkziel geändert, indem der Adresse der Parameter "js" mit dem Wert 1 hinzugefügt wird. In Formulare wird ein verstecktes Feld mit dem Namen "js" und dem Wert "1" eingehangen.
Wie man unschwer erraten kann, ist dieser Parameter "js" im Folgenden für PHP von Bedeutung.
- Es muss ein (I)Frame enthalten, in welches die Datei geladen wird, welche das Dokument manipulieren soll. Dieses wird beim Laden per write() in das Dokument geschrieben
- Daten zusammenstellen
Das angeforderte PHP-Skript muss 2 Fälle berücksichtigen.
Um zu entscheiden, was zu tun ist, prüft es, ob eine Variable $_GET['js'] übermittelt wurde. Ist dies der Fall, muss es den Javascript-Code zur Manipulation des Elterndokumentes ausliefern, andernfalls das komplette Dokument.
Das könnte bspw. folgendermassen vonstatten gehen:
- wir benutzen eine Reihe von PHP-Variablen, welche je nach angeforderter Seite variieren.
PHP-CodeCode:$titel = 'der Titel der Seite'; $hintergrund = '#414141';//Hintergrundfarbe der Seite $bild = '2.gif';//ein Bild $text = '<p>etwas dynamischer Text</p> <br>mit Zeilenumbruch';
- weiterhin verwenden wir eine Vorlage für das HTML-Dokument
PHP-CodeCode:<html> <head> <title>$titel</title> </head> <body style="background-color:$hintergrund"> <img name="bild" src="$bild" alt="alt"><br> <a target="_self" href="seite1.php">Seite#1</a><br> <a target="_self" href="seite2.php">Seite#2</a><br> <a target="_self" href="seite3.php">Seite#3</a> <div id="text">$text</div> </body> </html>
Wurde die Variable $_GET['js'] nicht übermittelt, liefern wir an den Browser diese Vorlage, nachdem dort die entsprechenden Variablen-Werte eingefügt wurden, aus:
PHP-CodeCode:<?php if(!isset($_GET['js'])) { echo stripslashes( preg_replace( '/\$(\w+)/e', "\${'\\1'}", file_get_contents('pfad/zur/vorlage.txt'))); }
Andernfalls liefern wir den Javascript-Code aus(welcher sich korrekterweise in einem simplen HTML-Dokument befinden sollte) :
PHP-CodeCode:else { ?> <html> <head> <title>-</title> <script type="text/javascript"> <!-- if(self == top) { location.replace('<?php echo preg_replace('/js=1&?/', '', $_SERVER['REQUEST_URI']);?>'); } else { top.document.title = '<?php echo $titel;?>'; top.document.bild.src = '<?php echo $bild;?>'; top.document.getElementsByTagName('body')[0].style.backgroundColor = '<?php echo $hintergrund;?>'; top.document.getElementById('text').innerHTML = unescape('<?php echo rawurlencode(preg_replace('/\s+/',' ',$text));?>'); } //--> </script> </head> <body></body> </html>
Im Javascript-Code prüfen wir als erstes, ob das Dokument wirklich in einem Frame geladen wurde. Ist dies nicht der Fall(mögliche Ursache wäre bspw., dass ein Benutzer einen der manipulierten Links gebookmarkt hat), leiten wir die aktuelle Adresse um. Ziel ist dabei die aktuelle URL, allerdings nach Entfernen des Parameters $js aus dem QUERY_STRING.
Ist bis hierhin alles planmässig verlaufen, werden die nötigen Javascript-Anweisungen ausgegeben. Im Beispiel sind das:
- $title dem title-Attribut des Elterndokumentes(Anzeige der Titelleiste des Browsers) zuweisen
- Im Elterndokument ein Bild namens "bild" gegen $bild austauschen
- Die Hintergrundfarbe des <body> in $hintergrund ändern
- Dem Element mit der ID "text" den Inhalt von $text zuweisen
Dies ist nur eine kleine Auswahl, prinzipiell gibt es nichts, was man in einem Dokument nicht ändern könnte.
Zu beachten ist dabei, dass die eingelesenen PHP-Variablen keine Zeilenumbrüche enthalten dürfen.
Weiterhin müssen die Zeichen < > / sowie je nach den im JS-Code verwendeten Begrenzern von Zeichenketten einfache oder doppelte Anführungszeichen entweder escaped, oder wie im hier gezeigten Beispiel per rawurlencode() codiert werden(im Javascript-Code können diese Zeichenketten per unescape() wieder dekodiert werden).
Das Escapen dieser Zeichen ist ein wenig umständlicher, ist hinsichtlich des Datenverkehrs jedoch vorteilhafter, da mit rawurlencode() eine Vielzahl von Zeichen codiert wird, bei denen es nicht nötig ist(die Datenmenge pro codiertem Zeichen verdreifacht sich)
- wir benutzen eine Reihe von PHP-Variablen, welche je nach angeforderter Seite variieren.
- Schlussbemerkungen:
Ob es wirklich sinnvoll ist, diese Methode anzuwenden, überlasse ich euch. Probiert es einfach mal aus.
Ich finde, dies ist, besonders wenn wenig Inhalt in umfangreichen Dateien ersetzt werden muss, ein interessanter Weg. Auch Designfetischisten, welche den Seitenübergang beim Neuladen als unästhetisch empfinden, dürften auf ihre Kosten kommen.
"Datenbankabfragen mit Javascript"... hiermit sind sie kein Problem.
Beim Probieren werdet ihr womöglich bemerken, dass nach dem Aktualisieren des Dokumentes über den Browser(F5 oder Reload-Button) das manipulierte Dokument verloren geht. Workaround: setzt einen Cookie mit der zuletzt angeforderten Seite, so könnt ihr in diesem Fall das korrekte Dokument ausliefern.
Live-Beispiel | Dateien zum Selbst-Testen