Stefan Mintert und Christine Kühnel (Hrsg.)
Workshop JavaScript
Bitte beachten Sie: Der originalen Printversion liegt eine CD-ROM bei. In der vorliegenden elektronischen Version ist die Lieferung einer CD-ROM nicht enthalten. Alle Hinweise und alle Verweise auf die CD-ROM sind ungültig.
An imprint of Pearson Education München • Boston • San Francisco • Harlow, England Don Mills, Ontario • Sydney • Mexico City Madrid • Amsterdam
Die Deutsche Bibliothek – CIP-Einheitsaufnahme Ein Titeldatensatz für diese Publikation ist bei Der Deutschen Bibliothek erhältlich. Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig auch eingetragene Warenzeichen oder sollten als solche betrachtet werden. Umwelthinweis: Dieses Produkt wurde auf chlorfrei gebleichtem Papier gedruckt. Die Einschrumpffolie – zum Schutz vor Verschmutzung – ist aus umweltverträglichem und recyclingfähigem PE-Material.
10 9 8 7 6 5 4 3 2 03 02 01 ISBN 3-8273-1718-5
© 2000 by Addison-Wesley Verlag, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D-81829 München/Germany Alle Rechte vorbehalten Einbandgestaltung: Rita Fuhrmann, Frankfurt/Oder Lektorat: Sylvia Hasselbach,
[email protected], Herstellung: TYPisch Müller, Arcevia, Italien Satz: reemers publishing services gmbh, Krefeld Druck: Bosch, Ergolding Printed in Germany
Inhaltsverzeichnis Vorwort. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
1 JavaScript und HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
1.1 1.2 1.3 1.4
Herkunft von JavaScript. . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Grundlagen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Übungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2 Die Sprache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1 2.2 2.3 2.4
Grundlagen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Übungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Tipps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3 Das Objektmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1 3.2 3.3 3.4
59
Grundlagen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 Übungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Tipps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
5 Formulare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1 5.2 5.3 5.4
39
Grundlagen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 Übungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Tipps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4 Eigene Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1 4.2 4.3 4.4
23
77
Grundlagen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Übungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 Tipps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
6 Event-Handling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 6.1 6.2 6.3 6.4
Grundlagen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tipps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
111 118 119 120
5
Inhaltsverzeichnis
7 Fenster und Frames . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 7.1 7.2 7.3 7.4
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tipps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
127 144 146 147
8 Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 8.1 8.2 8.3 8.4
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tipps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
159 163 164 166
9 Drei Einführungen in DHTML . . . . . . . . . . . . . . . . . . . . . . . . . . 171 9.1 9.2 9.3 9.4
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tipps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
171 189 198 202
10 DHTML: Cross-Browser-Strategien . . . . . . . . . . . . . . . . . . . . . . 223 10.1 10.2 10.3 10.4 10.5 10.6
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tipps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Anhang 1: Eine Checkliste für Netscape 4 . . . . . . . . . . . . . . Anhang 2: Kleine Cross-Browser-Referenz . . . . . . . . . . . . .
223 230 238 246 268 269
11 Erweitertes Event-Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 11.1 11.2 11.3 11.4
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tipps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
271 280 287 290
12 LiveConnect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 12.1 12.2 12.3 12.4
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tipps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lösungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
313 345 349 351
13 Bibliographie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365 Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
6
Für Brendan Eich, ohne den es diese Sprache nicht gäbe, und für das Web Standards Project, auf dass diese Sprache irgendwann problemloser zu benutzen ist.
Vorwort JavaScript, eine ehemals einfache Skriptsprache zur Einbettung in HTML-Seiten, ist das Thema dieses Buches. Als sie Ende 1995 im Navigator 2 erschienen ist, waren die Dinge tatsächlich noch einfach. In der Zwischenzeit hat JavaScript eine stürmische, ungeordnete, chaotische, kreative Entwicklung durchlaufen. Die Fähigkeiten, die die Sprache oder die mit ihr unmittelbar verbundenen Techniken heute bieten, übersteigen bei weitem das, was 1995 zunächst unter dem Namen LiveScript abzusehen war. Der Preis für diese Entwicklung und den heutigen Zustand ist hoch. Er ist bestimmt aus zum Teil undurchschaubaren Imkompatibilitäten zwischen den Browsern und einem komplexen Zusammenspiel von mehreren beteiligten Komponenten und Aspekten (Programmiersprache, Objektmodell, Auszeichnungssprache, StyleSheets, Applets, Plug-ins u.a.m.). Wie geht man diese Schwierigkeiten an? Die Antwort lautet: mit viel praktischem Know-how. Aus diesem Grund ist das Buch, das Sie in den Händen halten, nicht von einem einzelnen Autor geschrieben worden. Beide Herausgeber haben eine weit zurückreichende JavaScript-Erfahrung, Christine Kühnel bereits seit 1996, Stefan Mintert schon seit den Anfängen 1995. Dennoch wollten wir das Buch nicht alleine schreiben. Als die Idee für den JavaScript-Workshop entstand, gab es folgende Vorgaben des Verlags: kompakt, praxisrelevant, übungsbasiert sollte es sein, ganz im Sinne der gesamten Buchreihe. Dass die Eigenschaften kompetent und fachlich fundiert dazu gehören, versteht sich von selbst; andernfalls lohnt sich der Aufwand nicht, ein solches Projekt anzugehen. Was lag also näher, als Autoren zusammenzubringen, die den JavaScript-Workshop mit Leben füllen könnten? Wir betreuen bereits seit 1997 die FAQ zur deutschsprachigen JavaScriptNewsgruppe. Gemeinsam haben wir Autoren gefunden, die zum Beispiel in ihren Beiträgen in der Newsgruppe immer wieder bewiesen haben, dass sie die nötige fachliche Kompetenz mitbringen, einen Beitrag für unser Buch zu schreiben. Wir haben ein Grobkonzept für den Aufbau des Buches vorgegeben. Die Gliederung der Kapitel in Grundlagen, Übungen, Tipps und Lösungen ist eine Eigenschaft der Workshop-Reihe. Die inhaltliche Ausgestaltung der Kapitel haben wir in die Hände der einzelnen Autoren gelegt. Sie, die Leserin bzw. der Leser, finden im vorliegenden Buch daher eine Sammlung von Beiträgen, von deren fachlicher Qualität wir überzeugt sind. Mit dem Ziel, die Unabhängigkeit der Kapitel zu bewahren, haben wir andererseits versucht, durch die Zusammenstellung und die Reihenfolge einen Aufbau des Buches zu erreichen, der das sequentielle Durchlesen oder besser das Durcharbeiten von Anfang bis Ende erlaubt. Die Komplexität des Themas in Verbindung mit einem natürlich begrenzten Umfang verhindert leider eine vollständige Behandlung aller Aspekte und Ein-
9
Vorwort satzmöglichkeiten von JavaScript. Auch in diesem Punkt greifen wir auf die Erfahrung unserer Autoren zurück. Sie gewährleistet, dass der Leser die Auswahl an praxisrelevantem Wissen vorfindet, die er für den täglichen Einsatz als Web-Autor benötigt, und nicht mit den weniger wichtigen Fakten belästigt wird. Die angestrebte Kompaktheit des Buches setzt voraus, dass Sie sich mit benachbarten Techniken (wie HTML und ggf. Java) bereits auskennen. Mit den im Buch enthaltenen Übungen wollen wir Sie in die Lage versetzen, das Erlernte unmittelbar anzuwenden. Inwieweit uns das Erreichen all dieser Ziele gelungen ist, bleibt Ihrer Beurteilung überlassen. Über Kritik jedweder Art freuen wir uns selbstverständlich. Wie Sie die Autoren der Kapitel erreichen können, steht jeweils zu Beginn eines jeden Kapitels. Bitte beachten Sie jedoch, dass wir nicht ohne Weiteres JavaScript-Support leisten können. Die Erfahrungen mit der JavaScript-FAQ und anderen JavaScript-Veröffentlichungen haben gezeigt, dass das Mailaufkommen, das uns erreicht, nicht in vernünftiger Zeit zu bearbeiten ist. Aus diesem Grund bitten wir Sie, allgemeine Fragen zu JavaScript, die nichts mit diesem Buch zu tun haben, in der Newsgruppe zu stellen: de.comp.lang.javascript. Weitere aktuelle Informationen, auch dazu, wie Sie uns erreichen können, finden Sie im Web unter http://www.javascript-workshop.de/. Bitte sehen Sie zuerst dort nach, bevor Sie uns kontaktieren. Wir haben an dieser Stelle die Möglichkeit, stets unsere aktuellen Adressen u.Ä. anzubieten. Abschließend bleibt uns eine Verpflichtung, der wir gerne nachkommen, nämlich der Dank an Thomas Fischer, Michael Praast und Stefano Picco, die zwar leider nicht als Autoren dabei waren, uns aber unterstützt haben. Und selbstverständlich freuen wir uns, Ihnen nachfolgend die Autoren unseres Buches sowie uns selbst vorstellen zu können.
Stefan Mintert, Dortmund Christine Kühnel, Hengersberg Juli 2000
10
Die Autoren des JavaScript-Workshops
Christine Kühnel hat vor einigen Jahren das Netz für sich entdeckt, zuvor viele Jahre lang programmiert. Was lag also näher, als beides zu kombinieren und sich mit dynamischen Webseiten zu befassen? Die Idee, Antworten auf in der Newsgruppe oft gestellte JavaScript-Fragen in einer praxisorientierten FAQ im Web anzubieten, stammt von ihr. Stefan Mintert arbeitet mit JavaScript seit der Entstehung 1995. Neben einigen Artikeln erschien im Jahre 1996 sein erstes Buch, das erste nicht-englischsprachige JavaScript-Buch. Die Einrichtung der deutschsprachigen Newsgruppe zu diesem Thema geht auf seine Initiative zurück. Heute arbeitet er als selbständiger Berater im Bereich Internet, World Wide Web und Publishing mit dem Schwerpunkt XML sowie als Autor und Referent. Dietmar Rabich fand den Weg zum Internet und damit zu HTML und JavaScript vor einigen Jahren und folgt diesem Hobby seither. Die eigene Homepage spiegelt diese Themen wider. Schon vorher beschäftigte sich der Mathematiker beruflich wie privat lange Zeit – angefangen mit einem Wang 2200 (Kennt den noch jemand?) – mit Themen rund um die Datenverarbeitung: Programmierung, Datenbanken usw. So entstand von ihm als Coautor neben einigen Fachartikeln im Jahre 1987 das spätere Atari-Profibuch ST-STE-TT. Wolfgang Schwarz entwickelte im Frühsommer 1998 die Theorie der seinsschwangeren Selbstspiegelataraxie, die er, um unnötigen Rummel zu vermeiden, in einer kleinen Höhle in den Apenninen vergrub, wo er sich dem Senfanbau widmet und versucht, alte Zahlen auswendig zu lernen. Tobias Trelle hat an der Ruhr-Universität Bochum Mathematik studiert und war am dortigen Rechenzentrum vier Jahre lang als wissenschaftliche Hilfskraft beschäftigt. Seit 1995 ist er Teilhaber der CyCo Systems GmbH & Co. KG (http://www.cycosys.com/), einer Bochumer Firma, die auf 3D/Virtual RealitySysteme im Internet spezialisiert ist. Dort ist er als Anwendungsentwickler tätig. Die Icons im JavaScript-Workshop Folgende Icons werden Ihnen bei der Arbeit mit dem JavaScript-Workshop begegnen: Achtung: Dieses Icon warnt Sie vor Hürden, die JavaScript oder die BrowserHersteller aufgestellt haben. Beispiel: Als praxisorientiertes Buch besitzt der Workshop natürlich eine Vielzahl an Beispielen. Hinweis: Die Hinweise zeigen Ihnen, welchen Dingen Sie besondere Beachtung schenken sollten. Tipp: Unter den Tipps finden Sie wichtige Informationen für die Praxis.
11
Vorwort Die CD zum Buch Die CD, die Sie in diesem Buch finden, enthält die größeren, lauffähigen Beispiele sowie die lauffähigen Musterlösungen zu den Übungsaufgaben. Darüber hinaus haben wir jedem Autor einen persönlichen Bereich überlassen, in dem er eigenes Material unterbringen kann. Darin sind viele interessante JavaScript-Beispiele zu finden, die Sie sich unbedingt ansehen sollten. Außerdem finden Sie auf der CD als kleines Extra noch einen JavaScript-Test. Da dieser Test automatisch ausgewertet wird, versteht es sich von selbst, dass die Fragen nicht mit dem Anspruch unseres Buches mithalten können. Sollten Sie ernsthaft bemüht sein, JavaScript zu erlernen, empfehlen wir das Durcharbeiten des Buches inklusive der darin enthaltenen Übungen. Gelegentlich werden Sie auch noch einen Blick in die online verfügbare JavaScript-Dokumentationen werfen müssen; den Link finden Sie auf unserer Buchseite. Während der Arbeit mag der Test als kleine Motivation dienen, damit Sie sich dann umso interessierter auf die herausfordernden Übungen im Buch stürzen.
12
1
JavaScript und HTML Von Stefan Mintert,
[email protected] 1.1
Herkunft von JavaScript
Im Jahre 1995 hat die Firma Netscape begonnen, für ihren Web-Browser Navigator eine Programmiersprache zu entwickeln, die den bis dahin unveränderlichen Web-Seiten Leben einhauchen sollte. Auf die Frage, weshalb denn eine neue Sprache entwickelt werden sollte und weshalb nicht etwa Perl verwendet werden könne, antwortete der maßgebliche Entwickler Brendan Eich in einer Newsgruppe: »Ich mag Perl sehr. Ich hatte aus Marketing-Gründen nicht die Möglichkeit, sie in den Navigator einzubetten, als ich JavaScript erschuf. Die Netscape-HTML-Skriptsprache sollte oberflächlich Java-ähnlich sein [...]. Mein erster Gedanke war übrigens, Scheme einzubauen, aber das wurde ebenfalls aus Marketing-Gründen abgelehnt sowie aus unbewiesenen, aber intuitiven Ängsten hinsichtlich der einfachen Handhabbarkeit, die auch für Perl gelten.« Ebenso wie die Entscheidung für die Sprache selbst mehr vom Marketingstandpunkt aus getroffen wurde, so wurde auch der Name gewählt. Ursprünglich war der Name LiveScript, was in die Linie mehrerer »Live«-Produkte von Netscape passte (LiveWire, LivePayment, LiveConnect usw.). Der Erfolg von Java und ein entsprechendes Abkommen mit der Firma Sun sorgte dann für den endgültigen Namen JavaScript. Ein wirklicher Bezug zu Java ist damit nicht gegeben, von gewissen syntaktischen Ähnlichkeiten abgesehen.
JavaScript ist nicht Java »light«
Der erste Web-Browser, der JavaScript-Programme ausführen konnte, war der Navigator 2. Ab der Version 3 hat auch Microsoft mit dem Internet Explorer die Sprache unterstützt, allerdings aus rechtlichen Gründen unter dem Namen JScript. Eine dritte Bezeichnung kam dann 1996 ins Spiel, als Netscape zusammen mit dem europäischen Konsortium ECMA eine standardisierte Form schaffen wollte. Das Ergebnis dieser Arbeit heißt heute ECMAScript. Mittlerweile ist die Sprache unter dem einen oder anderen Namen weit verbreitet. Neben den Web-Seiten können z.B. auch PDF-Dokumente JavaScript (bzw. ECMAScript) enthalten. Eine ausführliche Auflistung der wichtigen Stationen der Entwicklung ist im Netz unter der Adresse http://www.mintert.com/javascript/ zu finden.
JScript ECMAScript
1.2
Grundlagen
In diesem Buch geht es um JavaScript als Programmiersprache für das WebPublishing. Viele Anwendungsmöglichkeiten und Einsatzgebiete lassen wir außen vor, um uns auf die Verwendung der Sprache zusammen mit HTML zu
13
1 JavaScript und HTML konzentrieren. HTML betrachten wir hier als die Dokumentensprache für das World Wide Web; dass es verschiedene Versionen und Dialekte gibt, spielt hier keine Rolle. Ebensowenig die zukünftigen Möglichkeiten, die sich durch XML ergeben1. Als zu Grunde liegende HTML-Version verwenden wir hier XHTML 1.0, also eine Version, die funktionsgleich zu HTML 4 ist. Da es aber in diesem Buch nicht um HTML geht, werden wir im folgenden keine exakte Unterscheidung der Versionen machen und in den Beispielen einen pragmatischen Ansatz wählen. Im Einzelfall kann das bedeuten, dass wir vielleicht nicht ganz standardkonform sind, einfach deshalb, weil sich die Web-Browser nicht an Standards halten. Unsere Beispiele laufen dafür auch in der Praxis, nicht nur in der Theorie2. Für weitere Informationen zu HTML verweisen wir zum Beispiel auf [RAGG98] und natürlich auf http://www.w3.org/MarkUp/. Genug der Vorrede, kommen wir zur Sache. JavaScript-Programme werden in HTML-Dokumente eingebettet. Dazu muss es natürlich irgendein Element geben, damit der Text in einer Seite von dem Programm unterschieden werden kann. Das zuständige Element in HTML trägt den Namen script. Im einfachsten Fall sieht es so aus: <script> ... hier steht das JavaScript-Programm ...
Das Element darf (fast) überall in einer Webseite enthalten sein, wobei es für viele Zwecke sinnvoll ist, script im Element head unterzubringen. Außerdem sagt der Name des Elements noch gar nichts darüber aus, um welche Programmiersprache es sich bei dem Inhalt handelt. Zwar geht es hier nur um JavaScript, es könnte aber auch eine andere Sprache sein. Der Internet Explorer kann beispielsweise auch VBScript ausführen3. Wir müssen dem Browser also noch einen Hinweis geben, welche Art von Inhalt er in script vorfindet. Dazu gibt es zwei Attribute, language und type. Das erste wurde von Netscape eingeführt, das zweite ist eine Vorgabe des HTML-Standards, die in der Praxis noch nicht relevant ist, die man aber schon jetzt einhalten kann. Damit ergibt sich als Muster für eine HTML-Datei mit einem JavaScript-Programm die folgende Darstellung: ... der Titel des Dokuments ... <script language="JavaScript" type="text/javascript"> ... hier steht das JavaScript-Programm ... 1. Dies gilt umso mehr, als dass momentan (Mitte 2000) und auf absehbare Zeit XML-Dokumente im Browser (auch) in HTML gewandelt werden. Eine Kombination von XML mit einer Scriptingsprache wie JavaScript ist noch nicht implementiert. 2. Von Tipp- und weiteren Fehlern abgesehen. 3. Beim Navigator kann an Stelle von »JavaScript« auch heute noch »LiveScript« stehen. Von der Verwendung ist jedoch abzuraten.
14
Grundlagen ... hier steht die HTML-Seite ...
Diese Position von script – im Kopf der Seite – bietet sich an, um dort Funktionen, Variablen und weitere Datenstrukturen zu definieren, die zu einem späteren Zeitpunkt verwendet werden. Beispiele dafür werden die nachfolgenden Kapitel liefern. Eine wichtige Aufgabe von JavaScript-Programmen besteht darin, auf Ereignisse zu reagieren. Ereignisse können zum Beispiel Aktionen des Benutzers sein, wie etwa das Anklicken eines Buttons oder das Ändern eines Formularfeldes. Um darauf reagieren zu können, wurden mit JavaScript besondere Attribute für einige HTML-Elemente definiert, die so genannten Event-Handler. Auch in diesen Attributen können JavaScript-Anweisungen stehen, die bei Eintreten des Ereignisses ausgeführt werden. In Kapitel 6 erfahren Sie mehr darüber. An dieser Stelle nur ein kleines Beispiel:
Ereignisse
Noch einmal zusammengefasst: JavaScript-Anweisungen dürfen in HTML 왘 im Element
<script>... und
왘 in besonderen Attributen, den Event-Handlern, stehen.
Mit diesen wenigen Informationen haben wir nun schon genug zusammen, um endlich einmal ein erstes lauffähiges Beispiel zu zeigen. Zwar haben wir noch nicht alles erklärt, was in Listing 1.1 zu sehen ist. Aber zusammen mit der Abbildung ist zu erkennen, dass das Programm nach dem Laden ein kleines Fenster öffnet und den Leser begrüßt. JavaScript-Hello-World <script language="JavaScript" type="text/javascript"> function begruessung() { alert("Willkommen bei JavaScript!"); } Das erste JavaScript-Beispiel Listing 1.1: Ein Hello-World-Beispiel in JavaScript (beispiel0101.html)
15
1 JavaScript und HTML Abbildung 1.1: Das Hello-WorldBeispiel nach dem Laden der Seite
JavaScriptBibliotheken
Möglicherweise möchten Sie die oben definierte Funktion begruessung() auch in anderen Webseiten verwenden. In so einem Fall bietet es sich an, die Funktion(en) nicht in die Seite einzubetten, sondern sie in einer externen Datei zu speichern und diese Datei einzuladen. Genauso, wie Bilder auch nicht in der HTML-Datei stehen, kann man auch JavaScript-Programme extern speichern. Angenommen, eine solche Datei (mit unserer begruessung()-Funktion) trägt den Namen kernfunktionen.js, die im gleichen Verzeichnis wie die HTMLDatei steht, dann lässt sich obiges Beispiel folgendermaßen umschreiben: JavaScript-Hello-World <script language="JavaScript" type="text/javascript" src=”kernfunktionen.js”> Das erste JavaScript-Beispiel Listing 1.2: Das Hello-World-Beispiel mit externer JavaScript-Datei
Auf eine Abbildung können wir hier verzichten, da sich das gleiche Ergebnis einstellt wie bei dem Listing zuvor.
16
Grundlagen Sie wissen sicherlich schon, dass nicht alle Browser JavaScript beherrschen. Es stellt sich daher die Frage, was machen diese Browser mit unserem JavaScriptProgramm? Mit etwas Glück lautet die Antwort: Ignorieren! Wenn der WebBrowser neueren Datums ist, wird er das Element script kennen und den Inhalt einfach übergehen. Eventuell kommt das Programm aber auf die Idee, den Inhalt anzuzeigen. Dann würde der Leser unserer Web-Seite also die JavaScript-Anweisungen zu sehen bekommen. Das ist zweifellos nicht das, was wir wollen. Um das zu verhindern, muss der Inhalt von script in HTML-Kommentare eingebettet werden: <script language="JavaScript" type="text/javascript"> für alte Browser unsichtbar (HTML-Kommentar). Zu beachten ist hierbei noch, dass das Ende durch // eingeleitet wird. Dabei handelt es sich um einen JavaScript-Kommentar, der verhindert, dass die Zeichen »Ende: Inhalt verstecken -->« als Teil des Programms angesehen werden. Wir müssen hier also zwei verschiedene Kommentare einsetzen, HTML-Kommentare und JavaScript-Kommentare. Wem das zu kompliziert ist, kann schlicht und einfach das obige Muster kopieren und als Startpunkt für eigene Seiten benutzen. Eine weitere Form des JavaScriptKommentars soll an dieser Stelle aber auch noch erwähnt werden. Um mehrere Zeilen in einem Programm (nicht in HTML!) auszukommentieren, schließt man diese in die Zeilen /* und */ ein.
Kommentare
Es ist zwar schön, dass wir nun in der Lage sind, unser Programm vor nicht JavaScript-fähigen Browsern zu verstecken, noch schöner wäre es aber, wenn wir dem Benutzer noch eine Information darüber mitgeben könnten. Auch das ist möglich, und zwar mit dem Element noscript1. Der Inhalt wird nur dann angezeigt, wenn JavaScript ausgeschaltet oder nicht vorhanden ist. Probieren Sie es mit der auf der beiliegenden CD enthaltenen HTML-Datei aus. JavaScript-Hello-World <script language="JavaScript" type="text/javascript">
Da es sich bei JavaScript, im Gegensatz zu HTML, um eine Programmiersprache handelt, ist ein Punkt wichtig: Was passiert bei Fehlern im Programm? Das genaue Verhalten hängt vom Web-Browser2 ab, der das Programm ausführt. Normalerweise wird der Fehler angezeigt und die Programmverarbeitung wird beendet. Bei den neueren Versionen des Navigators erscheint jedoch nicht automatisch eine Fehlermeldung, um den arglosen Web-Surfer nicht gleich zu erschrecken. Stattdessen sieht man den Fehler nur noch in der JavaScript-Konsole, einem eigenen Fenster. JavaScript-Konsole
Die JavaScript-Konsole (im Navigator) wird über die manuelle Eingabe des Pseudo-URLs javascript: aufgerufen. Der Navigator kann so konfiguriert werden, dass die JavaScript-Konsole im Fehlerfall automatisch angezeigt wird. 1. Leider muss auch hier gesagt werden, dass die Praxisrelevanz fraglich ist. In der Praxis ist es in der Regel angeraten, ein gewünschtes Feature ganz direkt abzufragen, um sicherzugehen, dass es vorhanden ist. Wie das geht, zeigen spätere Kapitel. Wer sich auf die Versionsnummern verlässt, ist oft verlassen. 2. Oder allgemeiner: es hängt vom Interpreter ab.
18
Übungen Dazu schließen Sie zunächst alle Anwendungsfenster des Navigators. Danach fügen Sie in der Konfigurationsdatei pref.js folgende Zeile ein: user_pref(”javascript.console.open_on_error”, true);
Diese Konfigurationsdatei finden Sie auf Windows-Systemen in der Regel unter C:\Programme\Netscape\Users\\pref.js
und unter UNIX-Systemen in Ihrem Home-Verzeichnis unter ~/.netscape/preferences.js
Um die Änderungen bei Bedarf wieder rückgängig zu machen, löschen Sie einfach die zuvor eingefügte Zeile wieder. Für die Entwicklung eigener JavaScriptProgramme bietet es sich jedoch an, die automatische Fehleranzeige eingeschaltet zu lassen. Beim Internet Explorer 5 werden Fehler in jeweils einem eigenen Fenster angezeigt. Die Anzeige kann wahlweise ein- oder ausgeschaltet werden.
1.3
Übungen
leicht
1. Übung
An welchen Stellen dürfen in einem HTML-Dokument JavaScript-Anweisungen stehen? leicht
2. Übung
Wie heißt das Attribut des script-Elements, mit dem externe JavaScriptDateien geladen werden? 1. Das Attribut heißt import. 2. Das Attribut heißt include. 3. Das Attribut heißt src. 4. Das Attribut heißt onLoad. leicht
3. Übung
Über welches Attribut des script-Elements wird die JavaScript-Version angegeben? 1. Über das Attribut language. 2. Über das Attribut lang. 3. Über das Attribut version. 4. Über das Attribut type. Wie beurteilen Sie die Zuverlässigkeit der Versionsangabe? 19
1 JavaScript und HTML leicht
4. Übung
Warum sollte der Inhalt des script-Elements in HTML-Kommentare gefasst werden? leicht
5. Übung
Wie lassen sich Texte in einem JavaScript-Programm auskommentieren? leicht
6. Übung
Wie schreibt man die HTML-Kommentare im script-Element? leicht
7. Übung
In welches Element schreibt man alternativen Text, der angezeigt werden soll, falls der Browser nicht JavaScript-fähig ist?
1.4
Lösungen
Lösung zu 1 JavaScript-Anweisungen dürfen im Element script stehen, in externen Dateien (gelegentlich JavaScript-Bibliotheken genannt) sowie in besonderen Attributen von HTML-Elementen. Lösung zu 2 Das Attribut des script-Elements, mit dem externe JavaScript-Dateien geladen werden können, heißt src. Beispiel: <script language="JavaScript" type="text/javascript" src="meine-datei.js">
Lösung zu 3 Die JavaScript-Version wird über das Attribut language des script-Elements angegeben. Beispiel: <script language="JavaScript"> <script language="JavaScript1.1"> <script language="JavaScript1.2">
Der Anfang des HTML-Kommentares ist Qudratzahlen <pre> <script language="JavaScript" type="text/javascript">
28
Grundlagen Sichtbarkeit von Variablen <pre> <script language="JavaScript" type="text/javascript">
Die Ausgabe des Programms ist: Ausgabe Quadrat Quadrat Quadrat Quadrat Quadrat Quadrat Quadrat Quadrat Quadrat Quadrat
der von von von von von von von von von von
Quadratzahlen von 0 bis 9: 0 ist: 0 1 ist: 1 2 ist: 4 3 ist: 9 4 ist: 16 5 ist: 25 6 ist: 36 7 ist: 49 8 ist: 64 9 ist: 81
Lösung zu 2 <pre> <script language="JavaScript" type="text/javascript">
Die Ausgabe des Programms ist identisch zur Ausgabe der Lösung zu Übung 1. Lösung zu 4 JavaScript-Workshop: Musterlösung <script language="JavaScript" type="text/javascript">
Je nachdem, was der Besucher eingibt, erhält er beispielsweise folgende HTML-Seite als Ergebnis:
35
2 Die Sprache Abbildung 2.1: Ausgabe der Musterlösung zu Übung 4
Lösung zu 5 Das folgende Listing zeigt eine Musterlösung für die Übung 5. In der anschließenden Abbildung 2.1 ist die Ausgabe des Programms im Navigator zu sehen. Sie erkennen, dass das Durchlaufen mit numerischem Index nicht funktioniert, weil der length-Wert Null ist. JavaScript-Workshop: Musterlösung <script language="JavaScript" type="text/javascript"> <pre><script language="JavaScript" type="text/javascript">
Probieren Sie aus, bauen Sie das hier kurz ein: Eigene Variablen finden sich im Objektbaum
alert(window.kontonummer); alert(window.kontoinhaber); alert(window.kontostand);
Das Ergebnis ist deutlicher als es jede Erklärung sein könnte. Ihre Variablen sind Eigenschaften von window. Das gilt so natürlich nur für Variablen, die auch im ganzen Fenster bekannt sind. Erinnern Sie sich bitte an Kapitel 2 und daran, welche Variablen wo sichtbar sind. Nur was der Browser kennt, kann man verwenden Bis hierher ist Ihnen ganz bestimmt klar geworden, dass man auf die Objekte angewiesen ist, die der Browser anbietet. Es muss ein window geben, ein document, document muss ein Array forms[] enthalten usw. Skripte, die Sie für das Web schreiben, laufen nicht in einer bekannten oder bestimmbaren Umgebung, sondern auf dem Client. Im Voraus wissen Sie nicht, welchen Browser der Besucher der Seiten verwendet, kennen also die Umgebung nicht, in der die Skripts angestoßen werden. Nun ist die Existenz der Objekte aber Voraussetzung, es stellt sich also eine wichtige Frage:
50
Grundlagen
3.1.3
Sind die Objektmodelle der Browser identisch?
Nein! JavaScript ist vergleichsweise jung. Was Navigator 2 von Netscape, der erste Browser mit JavaScript-Fähigkeit, konnte und kannte, waren allererste Anfänge. Die Entwicklungen, die man insbesondere bei den Browsern von Netscape und Microsoft seither beobachten kann, sind gravierend. Die Sprache selbst hat sich dabei gar nicht so sehr gewandelt, die Objektmodelle sind es, die sich so stürmisch verändern. Betrachtet man das OM des Navigator 2 heute, so nimmt es sich vergleichsweise bescheiden aus. Und die Entwicklung steht ganz sicher noch lange nicht still. Für uns, die wir JavaScript für das Web einsetzen, heißt das, wir müssen mit Unterschieden leben, sie kennen und berücksichtigen. Seiten, mit denen der Besucher nichts anfangen kann, die ihn vielleicht sogar mit Fehlermeldungen verjagen, gehören zum Unangenehmsten, was uns passieren kann. Welcher Browser bietet was? Ich würde Ihnen diese Frage gern beantworten, wenn ich es könnte. Ich fürchte allerdings, eine nur annähernd vollständige Liste füllt ein eigenes wesentlich dickeres Buch als dieses hier. Hilfreich ist es, etwas zur Geschichte der Browser zu wissen: 왘 Das, was der erwähnte Navigator 2 konnte und kannte, wird nachträglich als JavaScript 1.0 bezeichnet.8 왘 Etwas später zieht Microsoft mit dem Internet Explorer 3 nach, was die
JavaScript-Fähigkeit der Browser anging. Die Unterschiede zum Navigator 2 sind nicht wirklich gravierend, zumindest aus heutiger Sicht nicht mehr. 왘 Danach kommt der nächste von Netscape, die Version 3. Er bringt die ers-
ten wirklichen Neuerungen mit. Mit ihm erscheint auch die JavaScript-Versionsangabe und die Möglichkeit, sie in <script> anzugeben. Und mit ihm kommt das Image-Objekt.
Ab hier gibt es das Image-Objekt
왘 Den Umfang, den NN3 in seinem OM anbietet, nimmt dann auch MS in Version 4 seines Internet Explorers auf. 9 왘 Aber das ist nicht alles, was MS seiner Version 4 mitgibt. Obwohl etwas
jünger als NN4, folgt MSIE4 von jetzt an Netscape nicht mehr.
8. Die Versionsangaben von Netscape beziehen sich nicht ausschließlich auf die Sprache, sondern eben auch auf die Objektmodelle der Browser. Das ist vom Begriff her sicher etwas irreführend. 9. Man macht an dieser Stelle in der Praxis sehr oft einen Schnitt, unterscheidet Browser, die JavaScript 1.1 nicht kennen, von denen, die es kennen. Ganz praktisch landen dabei NN2 und MSIE3 in der Gruppe der »Alten« (Browser anderer Anbieter einmal nicht beachtet).
51
3 Das Objektmodell 왘 Sind es bis dahin Details, die Browser unterscheiden, und lediglich Erweite-
rungen in JavaScript 1.1 seitens Netscape, so beginnt zu dieser Zeit das, was den JavaScript-Programmierern fortan gelegentliche Kopfschmerzen bescheren soll: Netscape und Microsoft gehen getrennte Wege. Alles, was von nun an Neues kommt, ist nicht mehr wirklich vergleichbar. Dennoch kann man vieles, was über die Fähigkeiten von NN3 hinausgeht, sowohl mit Netscape- als auch mit MS-Browsern erreichen. Man muss dabei allerdings mit schon vom Ansatz her unterschiedlichen Objektmodellen arbeiten. Dafür, diese Unterschiede am Ende doch unter einen Hut zu bringen, hat sich der Begriff Crossbrowser-Programmierung eingebürgert. Schlagen Sie jetzt bitte nicht dieses Buch verzweifelt zu. All das spielt bei einer Vielzahl der Anwendungen noch gar keine Rolle. Erst wenn Sie tiefer einsteigen, sich mit DHTML beschäftigen, werden Sie nicht umhin können, sich damit eingehend zu befassen. Die Kapitel 9 bis 12 in diesem Buch werden Ihnen auch dabei helfen. DOM – Document Object Model
Und noch eine gute Nachricht: Es gibt bereits Licht am Ende des Tunnels. W3C hat ein Document Object Model (DOM) spezifiziert ([W3CDOM]), das Netscape 6 zugrunde gelegt hat, dem aber auch der Microsoft Internet Explorer 5 bereits weitgehend folgt. Die Kenntnis der Unterschiede ist gut und schön, nur nützt sie erst dann etwas, wenn man erkennen kann, welche Fähigkeiten der Browser des Besuchers hat. Damit sind wir bei der nächsten wichtigen Frage: Wie findet man heraus, was der Browser kennt?
Eine oft anzutreffende Methode ist es, zu ermitteln, welchen Browser der Besucher benutzt, um sich in den Skripten danach zu richten. Dafür verwendet man das navigator-Objekt. navigator
Blättern Sie bitte noch einmal an den Anfang dieses Kapitels zurück, in Abbildung 3.1 ist dieses Objekt erwähnt. Die wichtigsten Eigenschaften schauen wir uns am besten an, indem wir nachsehen, was bei einem bestimmten Browser in ihnen steckt. Probieren Sie das folgende Skript mit Ihrem Browser aus: navigator <script language="JavaScript" type="text/javascript">
52
Grundlagen navigator.userAgent+'
'); document.writeln('navigator.appCodeName ' + navigator.appCodeName+'
'); document.writeln('navigator.appVersion ' + navigator.appVersion+'
'); document.writeln('navigator.appName ' + navigator.appName+'
'); document.writeln('navigator.language ' + navigator.language+'
'); document.writeln('navigator.platform ' + navigator.platform+'
'); document.writeln(''); // --> <noscript> Ihr Browser kann leider ohne JavaScript keine Informationen über ein JavaScript-Objekt liefern.
Ich habe gerade Netscape 4.72 unter Windows 98 hier. Damit erhalte ich: navigator.userAgent navigator.appCodeName navigator.appVersion navigator.appName navigator.language navigator.platform
Mozilla/4.72 [en] (Win98; I) Mozilla 4.72 [en] (Win98; I) Netscape en Win32
Und mein Internet Explorer 5 zeigt: navigator.userAgent Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt) navigator.appCodeName Mozilla navigator.appVersion 4.0 (compatible; MSIE 5.0; Windows 98; DigExt) navigator.appName Microsoft Internet Explorer navigator.language undefined navigator.platform Win32
Sie sehen, es sind Strings, die man natürlich auf ihren Inhalt hin untersuchen kann, und Sie sehen auch, dass navigator.userAgent die wichtigsten Informationen enthält. Je nachdem, wonach Sie unterscheiden wollen, untersuchen Sie dazu vielleicht noch andere Eigenschaften. Dafür lässt sich nur schwer ein Schema angeben, die Gründe, warum man unterscheiden will, sind zu vielfältig.
53
3 Das Objektmodell indexOf() leistet gute Dienste
Weil es um Auswertung von Zeichenfolgen geht, sei hier noch ein nützliches Hilfsmittel kurz vorgestellt: indexOf() bietet sich geradezu an. Das ist eine Methode, die Objekte vom Typ String besitzen, also alle Zeichenketten. Sie gibt Ihnen die Anfangsposition einer enthaltenen Zeichenfolge zurück. Und das ist die allgemeine Syntax: stringName.indexOf(searchValue, [fromIndex])
Wichtig und nützlich ist die Tatsache, dass -1 zurückgegeben wird, wenn die gesuchte Zeichenfolge nicht enthalten ist. Die Benutzung ist denkbar einfach: if(indexOf(navigator.userAgent,' MSIE')>-1) alert('Aha, ein Browser von Microsoft!');
Einen Hinweis muss ich zur Browsererkennung über navigator-Objekt unbedingt anbringen: Es wird oft davon ausgegangen, dass navigator.appName die Erkennung von Netscape-Browsern ermöglicht. Das stimmt so nicht. Zum Beispiel findet man auch bei Opera dort »Netscape«. Verwenden Sie bitte deswegen nie ausschließlich navigator.appName! Eine Übersicht darüber, was bei einer Reihe von Browsern in den Werten des navigator-Objektes alles so steckt, finden Sie im Web unter http://www.netz-
notizen.de/javascript/tipps/navigator.html. Unterscheidung per navigatorObjekt ist nicht unbedingt der beste Weg
Ich verwende derartige Browsererkennungen nur selten. Es gibt eine sicherere Methode: Man fragt den Browser, ob er die benötigte Eigenschaft des Objektes kennt, bevor man sie verwendet. Damit ist man etwas weniger auf Kenntnisse über jeden einzelnen Browser angewiesen. if (document.images)
liefert zum Beispiel abhängig davon, ob es document.images gibt oder nicht, true oder false. Will man also vermeiden, dass Browser, die gar kein ImageObjekt kennen, die Besucher mit lästigen Fehlermeldungen nerven, statt Bildwechseleffekte zu zeigen, führt man die Anweisungen nur im true-Fall aus. Deswegen sind die Beispiele zum image-Objekt weiter oben gar nicht besucherfreundlich, denn gerade das image-Objekt kennen die ersten Browser tatsächlich nicht. Wir hätten besser vorher nach seiner Existenz gefragt, zum Beipiel so: Image-Objekt <script language="JavaScript" type="text/javascript">
54
Übungen // -->
Nun ist es sicher kaum praktikabel, nach jeder Eigenschaft zuvor zu fragen. Das ist auch nicht erforderlich. Auch hier ist Wissen um die Fähigkeiten der Browser sehr hilfreich. Man kann von der Existenz bestimmter Objekte ausgehend darauf schließen, dass auch anderes bekannt ist. Und genau das tun die meisten JavaScript-Programmierer auch. Das gerade erwähnte image-Objekt ist das Beispiel dafür. Es kam mit dem Navigator 3, also JavaScript 1.1, und man geht davon aus, dass ein Browser, der dieses Objekt kennt, auch alles andere beherrscht, was diese Version enthält.
3.2
Übungen
1. Übung Wer interpretiert Ihre Skripte? 1. Das Betriebssystem des Client-Rechners 2. Der Web-Server 3. Der Web-Browser Was ist neben der Sprache selbst die wichtigste Grundlage, auf die JavaScriptProgramme angewiesen sind? 2. Übung Wie könnte man den Browser veranlassen, eine andere Datei zu laden? 3. Übung Sie haben alternative Seiten vorbereitet: eine Version für Browser, die nichts mit JavaScript anfangen können oder nur etwa den Umfang von JavaScript 1.0 beherrschen, eine andere für alle anderen. Wie würden Sie dafür sorgen, dass jeder Browser die ihm zugedachte Seite zeigt?
55
3 Das Objektmodell 4. Übung Sie haben eine HTML-Datei, deren body so aussieht:
Sie wollen in das Textfeld des zweiten Formulars jeweils bei Klick auf einen der Buttons einen Text schreiben. Da das mit Hilfe der Funktion schreibeText() geschehen soll, haben Sie die Event-Handler, die den Aufruf der Funktion veranlassen sollen, schon mal vorsorglich eingefügt. Es fehlt noch die Funktion selbst. Schreiben Sie diese Funktion, wählen Sie aber jeweils eine andere Schreibweise, um das Textfeld anzusprechen.
3.3
Tipps
Tipp zu 2 Denken Sie bitte an das location-Objekt und dessen Eigenschaft href. Tipp zu 3 Das Image-Objekt wurde mit JavaScript 1.1 eingeführt. Tipps zu 4 Welcher Button angeklickt wurde, erkennen Sie am übergebenen Parameter. Für die Wahl der verschiedenen Schreibweisen denken Sie an Namen, Arrays und assoziative Arrays.
56
Lösungen
3.4
Lösungen
Lösung zu 1 Der Web-Browser interpretiert Ihre JavaScripts, und auf sein Objektmodell (OM) sind Sie angewiesen. Lösung zu 2 Diese Anweisung sorgt dafür, dass nach ihrer Ausführung der Wert von window. location.href den URL der zu ladenden Datei enthält und damit der Browser veranlasst wurde, diese Datei in das entsprechende Fenster (window) zu laden: window.location.href = 'andereSeite.html';
Lösung zu 3 Wir verwenden die Idee der vorherigen Übung, um weiterzuleiten, wenn der Browser über die geforderten Fähigkeiten verfügt. Wir prüfen in der Datei, die für Browser ohne oder mit zu geringen JavaScript-Fähigkeiten gedacht ist, ob document.images existiert, schließen wenn ja daraus, dass der Browser unsere Anforderungen erfüllt, und laden sofort die passende Datei. Weiterleitung <script> if (document.images) window.location.href = 'schoenereSeite.html';
Das ist die Seite für Browser ohne oder JavaScript oder mit etwas magerer JavaScript-Fähikeit.
Eine wichtige Anmerkung zu dieser Weiterleitung möchte ich Ihnen nicht vorenthalten: Diese Lösung zeigt das Prinzip, und sie funktioniert auch. Aber sie hat einen Nachteil: man kommt von der so geladenen Seite nicht per Back-Button weg. Probieren Sie es aus oder schauen Sie auf der CD nach, dort finden sie diese Lösung. Deswegen sollte man besser die Methode window.location.replace() benutzen, die dafür sorgt, dass die gerade aktuelle Seite nicht in die History aufgenommen wird. Leider existiert replace() aber erst seit JavaScript 1.1 Für den
57
3 Das Objektmodell Fall also, dass auch noch ältere Browser weitergeleitet werden sollen, kommt man nicht umhin, entweder die gerade gezeigte unschöne Lösung zu verwenden oder zusätzlich eine Unterscheidung einzubauen. Für unser Beispiel geht es ohne Unterscheidung; wir wollten ja ohnehin nur Browser weiterleiten, die etwas von JavaScript 1.1 verstehen. location. replace() erwartet als Parameter den URL der neuen Seite. Unser Script könnte also so aussehen: <script> if (document.images) window.location.replace('schoenereSeite.html');
Lösung zu 4 Diese drei Möglichkeiten, das Textfeld anzusprechen und dessen Wert zu verändern, sind mir gerade eingefallen: <script> function schreibeText(1) { if (x == 1) window.document.forms[1].elements[0].value = 'Bei Nacht'; if (x == 2) window.document.zweitesFormular.meinTextfeld.value = 'sind alle Katzen'; if (x == 3) window.document.forms['zweitesFormular']. elements['meinTextfeld'].value = 'grau.'; }
58
4
Eigene Objekte Von Christine Kühnel,
[email protected] 4.1
Grundlagen
Sie haben im vorangegangenen Kapitel gesehen, dass sich Ihre eigenen Variablen als Eigenschaften von window im Objektbaum wiederfinden. Damit ist die Frage nahe liegend, ob man auch eigene Objekte definieren kann. Natürlich kann man das. Im Interesse übersichtlicher, sauber strukturierter Programmierung ist es bei etwas komplexeren Aufgaben sogar sinnvoll, mit eigenen Objekten zu arbeiten.
4.1.1
Eigene Objekte definieren
Sie erinneren sich an diesesBuch und meinRoman, die Objekte aus Kapitel 3? Die wollen wir jetzt gemeinsam definieren. Zwei Dinge sind dafür erforderlich. Zunächst brauchen wir einen eigenen Objekttyp, in unserem Fall ist es das Buch. Die Aufgabe, diesen Typ zu definieren, übernimmt eine Funktion. In der Regel werden darin auch gleich die notwendigen Eigenschaften und Methoden angelegt. Außerdem benötigen wir einen Operator, der uns Instanzen, also unsere einzelnen Objekte vom Typ Buch aufbaut; dafür gibt es new, einen Operator, der ein Objekt anlegt mit Hilfe eines Konstruktors, so nennt man die Funktion, die den Objekttyp definiert. new sind Sie bereits in diesem Buch begegnet, denken Sie an
new-Operator
var meinArray = new Array();
Sie haben das Objekt meinArray angelegt, indem Sie mit Hilfe von new eine Instanz des Typs Array definiert haben. Die Funktion Array() ist z.B. so ein Konstruktor, nur mussten Sie sie nicht selbst schreiben, weil sie der Browser bereits vordefiniert enthält. Aber wir wollen jetzt eigene, dem Browser noch unbekannte Objekte definieren und müssen uns deswegen um einen geeigneten Konstruktor selbst kümmern. So könnte zum Beispiel die KonstruktorFunktion für den Objekttyp Buch aussehen:
KonstruktorFunktion
function Buch(pTitel, pVerlag, pSeiten) { this.Titel = pTitel; this.Verlag = pVerlag; this.Seiten = pSeiten; }
59
4 Eigene Objekte this-Keyword
Sie erkennen die Eigenschaften wieder, die wir bereits im vorangegangenen Kapitel betrachtet haben. Die Erklärung, was es mit this auf sich hat, bin ich Ihnen noch schuldig. this ist ein Keyword10, das immer auf das Objekt selbst weist, Sie können es auch innerhalb von Methoden jederzeit verwenden. Zusammen mit der Konstruktor-Funktion haben wir die Voraussetzungen, unsere einzelnen Objekte anzulegen, zum Beispiel: diesesBuch = new Buch(”JavaScript Workshop", ”Addison-Wesley", 300);
Als Parameter haben wir der Konstruktor-Funktion die konkreten Werte der Eigenschaften Titel, Verlag und Seiten für diesesBuch übergeben. Lassen wir uns jetzt die Werte auflisten, etwa mit <script language="JavaScript" type="text/javascript">
Wir erhalten eine solche Ausgabe: Das Buch »Workshop JavaScript« von »Addison-Wesley« hat ca. 300 Seiten. Objekt als Eigenschaft
Ist Ihnen aufgefallen, dass das noch gar nicht alle Eigenschaften sind, die uns an so einem Buch interessieren sollten? Richtig, der Einband fehlt. Der war insofern eine Besonderheit, dass er selbst wieder ein Objekt sein sollte. Wie geben wir dem Objekt diesesBuch eine solche Eigenschaft Einband? Sie ahnen es, wir benötigen einen Objekttyp und damit eine Konstruktor-Funktion function Einband(pFarbe, pArt) { this.Farbe = pFarbe; this.Art = pArt; }
Bevor wir uns darum kümmern, die Konstruktor-Funktion für Buch entsprechend zu ergänzen, denken wir gleich noch daran, dass wir auch eine Eigenschaft offen haben wollten, die uns sagt, ob das Buch geöffnet ist, wenn ja, auf welcher Seite es aufgeschlagen ist. Dafür bietet es sich geradezu an, einen Integer-Wert zu verwenden, man kann darin gleich die Seite unterbringen, auf der das Buch aufgeschlagen ist. 0 soll für »zugeschlagen« stehen.
10.Der englische Begriff, »Keyword«, ist in der Programmierung gebräuchlich, verzichten wir deshalb darauf, daraus ein »Schlüsselwort« zu machen.
60
Grundlagen Außerdem benötigen wir noch die Methoden für das Auf- und für das Zuschlagen. Methoden sind Referenzen auf Funktionen. Also gehen wir zunächst daran, die Funktionen zu schreiben.
Methoden sind Referenzen auf Funktionen
Was muss beim Zuschlagen passieren? Das ist nicht viel. Der Wert von offen muss lediglich auf 0 gesetzt werden: function zuschlagen() { this.offen = 0; }
Beim Aufschlagen müssen wir ein ganz klein wenig mehr tun, wir wollen ja eine bestimmte Seite erreichen. Die gewünschte Seite werden wir später beim Aufruf als Parameter übergeben: function aufschlagen(pSeite) { // erst mal nachsehen, ob es die Seite gibt if (0 < pSeite && this.Seiten 0) eval(this.options[this.selectedIndex].value)"> --- Bitte auswählen --Gruß Neues Fenster
Den Quelltext der Lösung finden Sie auf der CD unter \kapitel05\uebungen\ uebung0511.html. Lösung zu 12 Grundsätzlich ist die Gestaltung einer Funktion, die Felder eines Formulars belegt, kein Problem. Die Beispielfunktion zeigt dies mit einem Textfeld, welches mit dem aktuellen Datum gefüllt wird. // Vorbelegung function vorbelegung(formular_name) { var formular = document.forms[formular_name] var heute = new Date() formular.meinFeld.value = heute.toLocaleString() }
109
5 Formulare Die wichtigste Frage ist allerdings, wann bzw. wie diese Funktion aufgerufen wird. Man kann zum Beispiel am Ende des Dokuments die Funktion direkt aufrufen. Das ist zwar prinzipiell richtig, aber nicht ideal. Der geeignete Zeitpunkt ist der Event-Handler onLoad des HTML-Start-Tags . Er wird aktiviert, wenn das gesamte Dokument geladen ist. (Die Bilder zählen hier nicht mit, aber das ist für ein Formular auch gar nicht relevant.)
Das Formular in unserem Beispiel hat den Namen meinFormular. Den Quelltext der Lösung finden Sie auf der CD unter \kapitel05\uebungen\ uebung0512.html.
110
6
Event-Handling Von Dietmar Rabich, http://www.rabich.de,
[email protected] 6.1
Grundlagen
Zwischen einem HTML-Dokument und den in diesem Dokument enthaltenen Skripten ist eine Schnittstelle erforderlich, um gerade interaktive Möglichkeiten zu nutzen. Mit Hilfe eines Links und javascript: kann eine Funktion aktiviert werden, aber das ist bei weitem nicht ausreichend. Bestimmte Funktionalitäten sind gewünscht, wenn spezielle Ereignisse eintreten. Es soll zum Beispiel ein Bild ausgetauscht werden, wenn der Mauszeiger über einen Link bewegt wird, oder ein Skript muss initialisiert werden, wenn das Dokument geladen ist. Für diese ereignisorientierten Aufgaben gibt es die Event-Handler (Event = Ereignis). Sie werden bei dem gewünschten HTML-Element (zum Beispiel einem Link) hinzugefügt – und zwar immer genau diejenigen, die gerade benötigt werden. Im Prinzip sieht der Aufruf eines Event-Handlers folgendermaßen aus:
HTML-Element und Event-Handler
<Start-Tag ... onEvent="JavaScript-Quelltext">
Tritt bei dem durch das HTML-Element definierten Objekt das Ereignis auf, wird der zu dem Ereignis definierte JavaScript-Quelltext ausgeführt. Das HTML-Element, das Ereignis und der JavaScript-Quelltext müssen jeweils spezifiziert werden. Die Nutzung des Schlüsselworts this bezieht sich immer auf das zum HTML-Element korrespondierende JavaScript-Objekt. So besteht immer eine Zugriffsmöglichkeit auf dieses Objekt. Im Laufe der JavaScript-Versionen wurden übrigens immer mehr Event-Handler hinzugefügt bzw. waren bei immer mehr HTML-Elementen nutzbar. Die Skriptsprache bei den Event-Handlern ist nicht notwendigerweise JavaScript, auch andere Skriptsprachen sind verfügbar – allerdings bei weitem nicht so verbreitet. Trotzdem erscheint es sinnvoll, im Header des Dokuments folgende (seit HTML 4 verfügbare) Meta-Angabe aufzunehmen: <meta http-equiv="content-script-language" content="text/javascript">
Ein besonderes Augenmerk liegt außerdem auf dem verwendeten JavaScriptQuelltext. Da es bei den Event-Handlern keine direkte Unterscheidung der JavaScript-Versionen gibt, wie sie bei dem Start-Tag <script ...> mit dem Attribut language für das HTML-Element script angegeben werden kann, empfiehlt es sich, immer das niedrigste Level zu nutzen. Wer möchte schon gerne bei Browsern mit »nur« JavaScript 1.3 eine Fehlermeldung sehen, weil in dem JavaScript-Quelltext eines Event-Handlers JavaScript 1.5 verwendet wurde?
111
6 Event-Handling Sinnvoll sind Funktionsaufrufe, wobei die Funktion versionsabhängig definiert ist: ... <script language="JavaScript"> <script language="JavaScript1.3"> ... ... ...
6.1.1
Event-Handler
Event-Handler sind reichlich vorhanden. Ein praktischer Fall für den Einsatz: Sobald ein Dokument geladen wird, soll ein zweites, kleines Fenster mit zusätzlicher Information geöffnet werden. Das Ereignis ist das Laden des Dokuments. So kommt man auf den Event-Handler onLoad, der bei dem Start-Tag ergänzt wird. Also sieht der HTML-Start-Tag folgendermaßen aus:
Um einen Überblick zu ermöglichen, sind hier erst einmal die wichtigsten Event-Handler zusammen in einer Tabelle aufgeführt. Spezielle Eigenheiten einzelner Event-Handler sind im Anschluss aufgeführt.
112
Grundlagen Event-Handler Event-Handler
Beschreibung
onAbort
Führt den JavaScript-Quelltext aus, wenn das Laden eines Bilds abgebrochen wird.
onBlur
Führt den JavaScript-Quelltext aus, wenn ein Formularelement oder das Fenster den Eingabefokus verliert.
onChange
Führt den JavaScript-Quelltext aus, wenn ein Formularelement (Select, Text, Textarea) verändert wird.
onClick
Führt den JavaScript-Quelltext aus, wenn das entsprechende Objekt angeklickt wird.
onDblClick
Führt den JavaScript-Quelltext aus, wenn auf das entsprechende Objekt ein Doppelklick ausgeführt wird.
onError
Führt den JavaScript-Quelltext aus, wenn beim Laden eines Dokuments oder eines Bilds ein Fehler aufgetreten ist – beispielsweise wenn der URL des Bilds nicht korrekt war, bei dem der Event-Handler aufgeführt ist.
onFocus
Führt den JavaScript-Quelltext aus, wenn ein Fenster, Frame, Frameset oder Formularelement den Eingabefokus erhält.
onLoad
Führt den JavaScript-Quelltext aus, wenn ein Dokument oder alle Dokumente eines Framesets geladen wurden.
onMouseDown
Führt den JavaScript-Quelltext aus, wenn eine Maustaste gedrückt wird.
onMouseOut
Führt den JavaScript-Quelltext aus, wenn der Mauszeiger das Objekt verlässt, zu dem der Event-Handler gehört.
onMouseOver
Führt den JavaScript-Quelltext aus, wenn der Mauszeiger das Objekt betritt, zu dem der Event-Handler gehört.
onMouseUp
Führt den JavaScript-Quelltext aus, wenn eine Maustaste losgelassen wird.
onReset
Führt den JavaScript-Quelltext aus, wenn ein Formular zurückgesetzt wird (Reset).
onResize
Führt den JavaScript-Quelltext aus, wenn ein Fenster oder Frame in der Größe verändert wird.
onSelect
Führt den JavaScript-Quelltext aus, wenn Text in einem Text- oder Textarea-Feld selektiert wird.
onSubmit
Führt den JavaScript-Quelltext aus, wenn ein Formular abgeschickt wird.
onUnload
Führt den JavaScript-Quelltext aus, wenn ein Dokument verlassen wird.
Tab. 6.1: Übersicht über die Event-Handler
Die Übersicht ist nicht vollständig, denn mit neueren Versionen kommen auch weitere Event-Handler hinzu und je nach Browser sind zudem proprietäre Erweiterungen vorhanden. Interessant ist jedoch noch, bei welchen HTML-Elementen die Event-Handler häufig verwendet werden und ab welcher JavaScript-Version sie verfügbar sind. 113
6 Event-Handling Event-Handler und typische HTML-Elemente Tab. 6.2: Typische HTMLElemente und JavaScript-Versionen
Event-Handler
Typische HTML-Elemente
Verfügbar seit JavaScriptVersion
onAbort
img
1.1
onBlur
input, body
1.0 1.1 (Button, Checkbox, FileUpload, Frame, Password, Radio, Reset, Submit, window)
onChange
input, select
1.0 1.1 (FileUpload)
onClick
a, input type="button", input type="submit"
1.0 1.1 (return false ist möglich)
onDblClick
a
1.2
onError
img
1.1
onFocus
input, body
1.0 1.1 (Button, Checkbox, FileUpload, Frame, Password, Radio, Reset, Submit, window) 1.2 (Layer)
onLoad
img, layer, body
1.0 1.1 (Image)
onMouseDown
input type="button", a
1.2
onMouseOut
layer, a
1.1
onMouseOver
layer, a
1.0 1.1 (Area)
onMouseUp
input type="button", a
1.2
onReset
form
1.1
onResize
body
1.2
onSelect
input type="text", tex- 1.0 tarea
onSubmit
form
1.0
onUnload
body
1.0
Auch die Liste der HTML-Elemente ist nicht vollständig. Zudem ist seit HTML 4 bei nahezu jedem HTML-Element eine größere Palette Event-Handler nutzbar. Die Liste beschränkt sich auf diejenigen HTML-Elemente, bei denen die jeweiligen Event-Handler üblicherweise auftreten.
114
Grundlagen Einige Eigenheiten von speziellen Event-Handlern sollen allerdings noch hervorgehoben werden. Zudem ist der Einsatz diverser Event-Handler im Kapitel »Formulare« beschrieben, weil diese im Zusammenhang mit Formularen relativ häufig eingesetzt werden. Event-Handler onBlur Besonders zu beachten ist, dass der Eingabefokus auch dann verloren geht, wenn ein anderes Fenster als das aktuelle Browserfenster in den Vordergrund gebracht wird. Und das kann ziemlich schnell passieren!
Event-Handler onClick Bei diesem Event-Handler ist Rückgabewert möglich. Mit diesem wird gesteuert, ob die begonnene Aktion ausgeführt werden soll oder nicht. Hier ein Beispiel: Vor dem Wechsel auf eine spezielle Seite soll nachgefragt werden, ob der Wechsel wirklich ausgeführt werden soll. Das Ereignis tritt folglich bei dem Link auf. Da auf einen Klick mit der Maus reagiert werden soll, ist der richtige EventHandler onClick. Zudem muss ein Rückgabewert berücksichtigt werden, um eventuell den Wechsel auf die andere Seite noch verhindern zu können. Dietmar Rabich's Homepage
Event-Handler onError Hauptaufgabe ist die Steuerung von Fehlern. Beispielsweise ist es mit diesem Handler möglich, ein Bild, welches nicht geladen werden kann, durch ein anderes zu ersetzen.
Event-Handler onFocus Wie bei dem Event-Handler onBlur sei hier erwähnt, dass dieses Ereignis auch außerhalb des eigentlichen Dokuments ausgelöst werden kann. Event-Handler onLoad Bei Dokumenten tritt dieses Ereignis auf, wenn das Dokument vollständig geladen ist – bei einigen wenigen Browsern leider ohne Rücksicht auf Bilder etc.!
115
6 Event-Handling Ist er bei Framesets aufgeführt, erfolgt das Ereignis, wenn alle zum Frameset gehörigen Dokumente geladen sind. Nutzt man onLoad bei Bildern, so erfolgt der Aufruf bei dem jeweiligen Tag des HTML-Elements, wenn das Bild geladen ist.
Event-Handler onMouseOut Ein wichtiges Ereignis, das auftritt, wenn zum Beispiel der Mauszeiger einen Link verlässt. Interessant ist, dass das Pärchen onMouseOut und onMouseOver nicht mit der gleichen JavaScript-Version eingeführt wurde. Auch hier ein Beispiel: Der URL zu einem Link soll in der Statuszeile nicht angezeigt werden. Stattdessen soll es ein frei wählbarer Text sein. Der richtige Ort für das Ereignis oder die Ereignisse ist der Link. Der URL wird angezeigt, wenn sich der Mauszeiger darüber befindet und sonst nicht. Wenn der Mauszeiger über den Link bewegt wird, zündet der Event-Handler onMouseOver, wird er wieder wegbewegt, ist es onMouseOut. Mein Verweis
Event-Handler onMouseOver Das Ereignis tritt zum Beispiel auf, wenn der Mauszeiger einen Link betritt. Siehe dazu auch die Beschreibung des Event-Handlers onMouseOut. Event-Handler onResize Wird ein Fenster in der Größe verändert, so erfolgt ein Aufruf des Event-Handlers onResize. Neupositionierungen von Layern o.Ä. lassen sich so automatisch veranlassen. Event-Handler onSubmit Ein wichtiger Event-Handler im Zusammenhang mit Formularprüfungen, denn er wird vor Versand von Formulardaten aufgerufen. Durch den Rückgabewert, der bei diesem Event-Handler genutzt wird, lässt sich der Versand von Formulardaten im Fehlerfall unterbinden. Event-Handler onUnload Das Gegenstück zum Event-Handler onLoad stellt dieser Handler dar. Er kann zum Beispiel genutzt werden, um eventuell geöffnete zusätzliche Fenster zu schließen.
116
Grundlagen
6.1.2
Objekttyp Image
Was wären Event-Handler, wenn man sie nicht einmal an einem praktischen Beispiel ausprobieren könnte? Der Objekttyp Image erfreut sich großer Beliebtheit, denn es ist der Objekttyp, welcher für die Buttoneffekte genutzt wird. Um einen derartigen Effekt aufzubauen, bedarf es allerdings auch geeigneter Event-Handler. Aber nun zum Objekttyp selbst. Ein Objekt vom Typ Image wird automatisch mit jedem Bild innerhalb eines Dokuments angelegt. Jedes dieser Objekte kann mit Hilfe des Arrays document.images angesprochen werden. Da sich die Indizierung der Objekte und damit die Verwendung der Nummern mit jeder Änderung der Seite verändern kann, sollte man jedem Bild, welches man ansprechen möchte, mit Hilfe des Attributs name einen Namen geben. document.images["meinBild"]
Zusätzlich ist mit Hilfe des Konstruktors Image eine Anlage eines derartigen Objekts auch in Skripten möglich.
Neues ImageObjekt
new Image([Breite][, Höhe])
Die Breite und Höhe muss nicht unbedingt mit angegeben werden. In der Regel geschieht dies auch nicht. Noch erwähnt werden sollte, dass der Objekttyp mit den hier vorgestellten Eigenschaften mit der Version JavaScript 1.1 eingeführt wurde. Eigenschaften Die wesentliche Eigenschaft im Zusammenhang mit vielen Skripten ist src, hier aber trotzdem eine ausführlichere Liste: Eigenschaften eines Images Eigenschaft
Beschreibung
border
Breite des Rands; entspricht dem Attribut border des HTML-Elements
complete
Boole'scher Wert, der aussagt, ob ein Bild vollständig geladen wurde oder nicht.
lowsrc
URL des niedrigauflösenden Bilds; entspricht dem Attribut lowsrc des HTML-Elements und ist somit nur bei Browsern verfügbar, die auch dieses Attribut kennen
name
Name des Bilds; entspricht dem Attribut name des HTML-Elements
Tab. 6.3: Eigenschaften des Objekttyps Image
src
URL des Bilds; entspricht dem Attribut src des HTML-Elements
117
6 Event-Handling Ein neues Bild in einem Skript wird folglich so geladen: meinBild = new Image() meinBild.src = "neues_bild.gif"
Eigentlich ist der Begriff »laden« so zu verstehen, dass das Bild in den Cache des Browsers geladen wird. Ein Bild im Dokument passend zu tauschen geschieht folgendermaßen: document.images["vorhandenesBild"].src = meinBild.src
Dies setzt voraus, dass das Bild im HTML-Dokument den Namen vorhandenesBild bekam. Lädt man das Bild nicht vorab, so wird durch die dargestellte Zuweisung der Ladevorgang erst initiiert. Man »sieht« also, dass das Bild geladen wird, und eine Verzögerung stellt sich ein.
6.2
Übungen
leicht
1. Übung
Schreiben Sie ein Skript, welches Bilder vorab lädt. (Eine sinnvolle Anwendung wäre damit das Laden von Bildern zum Beispiel für eine Folgeseite. Die Zeit, in der die Seite betrachtet wird, vergeht so nicht ungenutzt.) leicht
2. Übung
Sie kennen den netten Effekt, dass sich das zu einem Link gehörige Bild ändert, wenn man den Mauszeiger über das Bild bewegt? Schreiben Sie ein solches Skript. mittel
3. Übung
Ändern Sie den Effekt aus der vorigen Übung so ab, dass ein drittes Bild erscheint, wenn man die Maustaste drückt. leicht
4. Übung
Schreiben Sie drei Funktionen, die jeweils eine Meldung liefern, wenn ein Bild geladen wurde, der Ladevorgang abgebrochen wurde oder der Ladevorgang fehlerhaft war. mittel
5. Übung
Schreiben Sie ein Skript, das eine JavaScript-gesteuerte Animation ablaufen lässt.
118
Tipps
6.3
Tipps
Tipps zu 1 왘 Vergegenwärtigen Sie sich die Möglichkeiten des Objekts Image. 왘 Suchen Sie sich ein HTML-Element und einen Event-Handler, der als Start
für die Ladefunktion dienen kann. Tipps zu 2 왘 Nutzen Sie die Erkenntnisse aus der vorigen Übung. 왘 Überlegen Sie sich geeignete Event-Handler und Stellen, an denen man sie
einbindet. Tipp zu 3 왘 Nutzen Sie wieder Erkenntnisse aus der vorigen Übung.
Tipp zu 4 왘 Überlegen Sie sich die passenden Event-Handler.
Tipps zu 5 왘 Nutzen Sie die Funktion setTimeout, um die Funktion für den Bildtausch
aufzurufen. (Die Funktion setTimeout führt den als ersten Parameter angegebenen JavaScript-Quelltext nach der als zweiten Parameter übergebenen Zeit – in Millisekunden – aus. Beispiel: setTimeout("und_los()", 5000)) 왘 Überlegen Sie sich eine Alternative zu Ihrer Lösung.
6.4
Lösungen
Lösung zu 1 Wichtig ist, sich erst einmal ein Array (oder andere geeignete Variablen) anzulegen, die die neuen Image-Objekte aufnehmen. var bilder = new Array()
Die Ladefunktion selbst besteht aus der Neuanlage eines Objekts vom Typ Image und dem Setzen einer passenden Quelle. function lade(img_name) { var anzahl_bilder
= bilder.length
119
6 Event-Handling bilder[anzahl_bilder] = new Image() bilder[anzahl_bilder].src = img_name }
Diese Ladefunktion muss für jedes Bild aufgerufen werden, welches vorab geladen werden soll, also noch eine Funktion. function vorabladen() { lade("bild1.gif") lade("bild2.jpg") lade("bild3.png") }
Diese Ladefunktion wird aktiviert, wenn das Dokument selbst geladen ist, also am besten mit dem Event-Handler onLoad bei dem HTML-Start-Tag .
Einen Gedanken sollte man noch der JavaScript-Version widmen. Insbesondere das genutzte Array ist erst ab JavaScript 1.1 verfügbar. Also muss auch das Skript mit der entsprechenden Version definiert werden. Damit es bei älteren Browsern nicht zu Problemen kommt, definiert man vor dem eigentlichen Skript eine leere Funktion mit passendem Namen. Diese wird bei passender JavaScript-Version vom folgenden Skript überschrieben. <script language="JavaScript">
Den Quelltext der Lösung finden Sie auf der CD unter \kapitel06\uebungen\ uebung0601.html. Lösung zu 2 Das Vorabladen kennen wir vom Prinzip her bereits aus der vorangegangenen Übung. Für jeden Button müssen zwei Bilder geladen werden, jeweils eines für den Normalzustand und eines, falls sich der Mauszeiger darüber befindet. Die Ladefunktion im Vergleich zur vorigen Lösung ein wenig abgewandelt: function button(dateiname_normal, dateiname_aktiv) { this.aktiv = new Image() this.normal = new Image() this.aktiv.src = dateiname_aktiv this.normal.src = dateiname_normal }
120
Lösungen Dazu wird eine passende Initialisierungsfunktion und das Array definiert. Das Bild in unserem Beispiel heißt übrigens meinButton. Praktischerweise wird dieser Name auch als Index für das Array genutzt. var bilder = new Array() function initialisierung() { bilder["meinButton"] = new button("bild1.gif", "bild2.gif") }
Letztlich wird bereits im Kopf des Dokuments diese Initialisierungsfunktion aufgerufen: initialisierung()
Damit ist der Ladevorgang abgeschlossen. Es fehlen noch zwei Funktionen, die helfen, das passende Bild zu setzen. Für das Bild im aktiven Zustand – also wenn sich der Mauszeiger darüber befindet – wäre das die Funktion function aktivbild(name) { document.images[name].src = bilder[name].aktiv.src }
und für das normale Bild die folgende Funktion function normalbild(name) { document.images[name].src = bilder[name].normal.src }
Neben dem Bild müssen auch die Event-Handler definiert werden. Die passenden finden sich bei dem Link: onMouseOver und onMouseOut.
Da auch hier JavaScript 1.1 Voraussetzung ist, muss darauf geachtet werden, Ersatzfunktionen für Browser mit älteren Versionen zu definieren. Bleibt vielleicht noch eine Frage zu klären: Warum wird die Funktion initialisierung diesmal nicht mit onLoad bei dem HTML-Start-Tag aufgerufen? Denken Sie einmal an den zeitlichen Ablauf. Erst wird das Dokument geladen – damit existiert auch der Link mit den Event-Handlern –, dann wird initialisiert. So könnte es passieren, dass der Link bereits genutzt wird, bevor
121
6 Event-Handling die Bilder und die JavaScript-Deklarationen dafür da sind! Mit dem Aufruf im Header werden diese Deklarationen bereits durchgeführt, bevor irgendein Bestandteil des Dokuments erscheint. Den Quelltext der Lösung finden Sie auf der CD unter \kapitel06\uebungen\ uebung0602.html. Lösung zu 3 Im Vergleich zur vorigen Übung wird erst einmal die Ladefunktion angepasst. Sie muss nun prinzipiell vier (eigentlich sogar nur drei) statt zwei Bilder laden. function button(dateiname_out, dateiname_over, dateiname_down) { this["over"] = new Image() this["out"] = new Image() this["down"] = new Image() this["up"] = new Image() this["over"].src this["out"].src this["down"].src this["up"].src
= = = =
dateiname_over dateiname_out dateiname_down dateiname_over
}
Bei der Initialisierungsfunktion ändert sich lediglich der Aufruf der gerade definierten Funktion button. bilder["meinButton"] = new button("bild1.gif", "bild2.gif", "bild3.gif")
Die Funktion, die für den Wechsel der Bilder sorgt, ist etwas allgemeiner gefasst: function bild(evnt, name) { document.images[name].src = bilder[name][evnt].src }
Letztlich ist auch der Link um zwei Event-Handler – onMouseDown und onMouseUp – zu erweitern: ...
Ansonsten gilt das, was schon bei der vorigen Übung genannt wurde.
122
Lösungen Den Quelltext der Lösung finden Sie auf der CD unter \kapitel06\uebungen\ uebung0603.html. Lösung zu 4 Die zentralen Event-Handler für diese Aufgabe sind die Event-Handler onLoad, onError und onAbort, die dem HTML-Tag des HTML-Elements hinzugefügt werden. Für die Meldung, falls das Bild erfolgreich geladen wurde, wäre das eine Funktion wie diese: function evnt_load(img) { alert("Bild" + ((img.name == "") ? "" : (" \"" + img.name + "\"")) + " geladen!") }
Im Fehlerfall sieht dies ähnlich aus: function evnt_error(img) { alert("Fehler beim Laden des Bilds" + ((img.name == "") ? "" : (" \"" + img.name + "\"")) + "!") }
Auch der Fall des abgebrochenen Ladevorgangs ist analog aufgebaut: function evnt_abort(img) { alert("Ladevorgang des Bilds" + ((img.name == "") ? "" : (" \"" + img.name + "\"")) + " abgebrochen!") }
Die Funktionen werden mit den passenden Event-Handlern aufgerufen:
Den Quelltext der Lösung finden Sie auf der CD unter \kapitel06\uebungen\ uebung0604.html. Lösung zu 5 Hier gibt es grundsätzlich zwei Lösungsansätze. Bei dem ersten wird eine Funktion per Event-Handler gestartet und danach läuft nur noch ein zeitgesteuertes Skript. Der zweite startet die Anzeige eines neuen Bilds jeweils eventgesteuert.
123
6 Event-Handling Die Bilder sollten alle die gleiche Größe haben. Allerdings muss der Dateityp nicht einheitlich sein. JPEG- oder PNG-Bilder sind ebenso wie GIF-Bilder gestattet. Beide Lösungen nutzen übrigens JavaScript 1.2-Funktionalitäten. Daher ist bei der Gestaltung der Skripte darauf zu achten, für ältere Browser Ersatzfunktionen vorzusehen. Lösungsansatz A
Der Ansatz geht davon aus, dass das Animationsskript an einer Stelle gestartet wird und dann abläuft. Dazu bietet sich der Start mit dem Event-Handler onLoad bei dem Start-Tag an. Die eigentliche Animation wird in einem Array abgelegt, jeweils mit dem Dateinamen datei, der Anzeigedauer zeit und einem Platzhalter bild für das korrespondierende Objekt vom Typ Image. function neues_bild(datei, zeit) { this.datei = datei this.zeit = zeit this.bild = null } var animation = new Array() animation[animation.length] = new neues_bild("bild1.gif", 2000) animation[animation.length] = new neues_bild("bild2.gif", 2000) animation[animation.length] = new neues_bild("bild3.gif", 2000)
Ferner wird eine globale Variable definiert, in der das aktuell angezeigte Bild gemerkt wird. var bild = -1
Die gesamte Animation wird vorab geladen, damit der Bildwechsel verzögerungsfrei erfolgt. function vorbereiten() { var i for(i = 0; i < animation.length; i++) { animation[i].bild = new Image() animation[i].bild.src = animation[i].datei } }
Die Startfunktion ist gleichzeitig die Funktion, die auch für den Bildwechsel zuständig ist. Hier wird der Bildzähler weitergezählt, das Bild ausgetauscht und
124
Lösungen am Ende die Funktion selbst wieder aufgerufen – mit dem passenden Zeitversatz. Das Bild trägt hier übrigens den Namen animation. function start() { bild++ if(bild >= animation.length) bild = 0 document.images["animation"].src = animation[bild].bild.src setTimeout("start()", animation[bild].zeit) }
Sowohl die Funktion für das Vorabladen der Bilder wie auch der Start der Animation wird mit dem Event-Handler onLoad des HTML-Elements body beim Start-Tag aktiviert.
Nicht zu vergessen, dass im HTML-Text ein Bild angelegt werden muss, damit das für das Skript notwendige Image-Objekt auch existiert:
Den Quelltext der Lösung finden Sie auf der CD unter \kapitel06\uebungen\ uebung0605a.html. Lösungsansatz B
Bei diesem Ansatz wird das Folgebild angefordert, wenn das aktuelle fertig geladen ist. Es bietet sich also wieder der Event-Handler onLoad an, diesmal allerdings eingesetzt bei dem HTML-Element . Dieser Lösungsansatz nutzt die gleichen Vorabladefunktionen wie der Lösungsansatz A. Lediglich der Bildzähler beginnt mit dem Wert 0 statt -1. var bild = 0
Die Funktion für den Bildwechsel ist in zwei Aufgabenteile getrennt. Der erste sorgt nur für den eigentlichen Wechsel: function wechsle_bild(name, nr) { document.images[name].src = animation[nr].bild.src }
Dann fehlt noch der Aufruf der gerade definierten Funktion und vorher natürlich das Weiterzählen. Der Aufruf des Folgebilds erfolgt hier zeitversetzt. function naechstes_bild(img) { bild++
125
6 Event-Handling if(bild >= animation.length) bild = 0 setTimeout("wechsle_bild('" + img.name + "'," + bild + ")", animation[bild].zeit) }
Sicherheitshalber wird die Funktion für die Vorbereitungen noch im Header aufgerufen. vorbereiten()
Das Bild für die Animation ist letztendlich noch durch den Event-Handler onLoad mit dem Aufruf der Funktion naechstes_bild zu erweitern.
Den Quelltext der Lösung finden Sie auf der CD unter \kapitel06\uebungen\ uebung0605b.html.
126
7
Fenster und Frames Von Christine Kühnel,
[email protected] 7.1
Grundlagen
Spätestens bei der Verwendung von Frames fragen Sie sich sicher, ob denn Informationen zwischen Frames ausgetauscht werden können, ob auf Objekte anderer Frames zugegriffen werden kann. Sie wollen auch mal hier und da ein Fenster selbst öffnen, vielleicht sogar anschließend Beziehungen zum Ursprungsfenster herstellen. Geht das alles? Natürlich geht das.
7.1.1
Das window-Objekt
Sie haben das Objekt window bereits kennen gelernt, wissen, dass es der Repräsentant eines Fensters oder auch eines Frames ist. An diesem Objekt hängt alles, um dieses Objekt dreht sich alles, was es in diesen Zusammenhang zu sagen gibt. Wir tun deswegen gut daran, uns etwas genauer damit zu befassen.
Alles dreht sich um das windowObjekt
Werfen wir also zunächst einen Blick auf die Eigenschaften und Methoden, die wir in diesem Kapitel benötigen werden (Tabelle 7.1 und Tabelle 7.2). Eigenschaften des window-Objektes: Eigenschaft
Beschreibung
document
repräsentiert das geladene Dokument
frames
Array, das für alle enthaltenen Frames je ein window-Objekt enthält
location
enthält Information über den aktuellen URL
name
Name des Fensters bzw. Frames
opener
Fenster, welches dieses hier geöffnet hat
parent
übergeordnetes Fenster
self
Referenz auf das Fenster selbst
status
Nachricht in der Statuszeile
Top
Fenster der obersten Ebene, immer Repräsentant des tatsächlichen Browserfensters
window
window-Objekt(e) (so vorhanden)
Tab. 7.1: Eigenschaften des window-Objekts
127
7 Fenster und Frames Die Liste aller Eigenschaften ist je nach Browser wesentlich länger. Ich habe mich hier auf diejenigen beschränkt, die bei allen Browsern anzutreffen sind. Nicht erwähnte Eigenschaften sind leider zumeist browserspezifisch. In der Praxis bedeutet das: verzichten oder Unterschiede berücksichtigen. Lassen Sie sich aber deswegen keine grauen Haare wachsen; für das, was mit Fenstern und Frames am häufigsten passiert, reichen die angeführten Eigenschaften. Auch dieses Kapitel werden wir damit weitestgehend bewältigen, lediglich ganz am Ende, wenn es um Grö ß en und Positionen geht, müssen wir an Unterschiede denken. Und dort beginnt sozusagen die Kür im Umgang mit Fenstern. Die Pflicht werden wir noch ohne das alles absolvieren können. Noch ein Wort zu self: self ist window »selbst«
Die self-Eigenschaft enthält eine Referenz auf das Fenster selbst. Das heißt, self.meineFunktion zum Beispiel ist identisch mit window.meineFunktion und self.irgendeinWert mit window.irgendeinWert. Ich benutze self gern; es ist so schön anschaulich, vom window »selbst« auszugehen. Finden Sie nicht auch?11 Methoden des window-Objektes:
Tab. 7.2: Methoden des window-Objektes
Methode
Beschreibung
Anmerkung
close()
schließt ein Fenster
moveBy()
bewegt ein Fenster um eine vorgegebene Distanz
ab MSIE4, NN4
moveTo()
bewegt ein Fenster zu einer vorgegebenen Position
ab MSIE4, NN4
open()
öffnet ein Fenster
resizeBy()
verändert die Grö ß e um vorgegebene Werte
ab MSIE4, NN4
resizeTo()
verändert die Grö ß e auf vorgegebene Werte
ab MSIE4, NN4
Muss ich es noch erwähnen? Auch diese Liste ist nicht vollständig. Ähnlich wie bei den Eigenschaften gibt es auch hier eine Reihe zusätzlicher browserspezifischer Methoden. Sie sehen, sogar einige der hier angegebenen kommen nicht ohne die Anmerkung aus, welche Browser sie kennen. Aber noch immer gilt: kein Grund zur Panik, erst ganz am Ende des Kapitels werden wir damit konfrontiert. Event-Handler Was fehlt uns neben Eigenschaften und Methoden des window-Objektes noch? Die Event-Handler. Wir benötigen hier nur einen: window.onload 11.Ich habe keine Idee, warum es so ist, weiß nur, dass es so ist: self wird gern mit this verwechselt. Dabei haben die beiden doch gar nichts miteinander zu tun!
128
Grundlagen In der Inline-Schreibweise im body-Tag ist er Ihnen bereits begegnet, etwa so:
Wie Sie wissen, feuert er, wenn das Dokument komplett geladen wurde. Das gilt sowohl für einfache Dokumente als auch für Framesets, bei denen bezieht sich »geladen« auf das gesamte Frameset:
7.1.2
Eigene Fenster verwenden
Jetzt aber erst einmal genug der grauen Theorie, wenden wir uns der Praxis zu, öffnen wir endlich unsere ersten eigenen Fenster. Das erledigt window.open() für uns; die allgemeine Syntax dafür sieht so aus: oNewWindow = window.open([sURL][,sName][,sFeatures]); 왘 oNewWindow legt den Namen des Objektes fest, über den Sie später Ihr
neues Fenster ansprechen können. 왘 sURL spezifiziert den URL des zu ladenden Dokuments. 왘 sName benötigen Sie, wenn das Fenster einen Namen haben soll, über den
Sie es per target in einem Link erreichen wollen. Bitte geben Sie hier Acht, sName und oNewWindow haben nichts miteinander zu tun. Das eine ist der Name, den Sie im HTML-Code verwenden können, das andere der Name Ihres Objektes in JavaScript. 왘 sFeatures gibt vor, wie das Fenster aussehen soll.
Bei sFeatures sollten wir etwas länger verweilen. Von Browser zu Browser müssen wir hier schon wieder mit Unterschieden leben. Am besten schauen wir uns das in der Übersicht in Tabelle 7.3: Parameter in window.open() an. Feature
Beschreibung
directories ={ yes | no | 1 | 0 }
Directory-Buttons
location ={ yes | no | 1 | 0 }
Adress-Zeile
menubar ={ yes | no | 1 | 0 }
Menü-Leiste
resizable ={ yes | no | 1 | 0 }
in der Grö ß e veränderbar*
scrollbars ={ yes | no | 1 | 0 }
Scrollleisten
status ={ yes | no | 1 | 0 }
Statuszeile
Browser
Tab. 7.3: Parameter in window.open()
129
7 Fenster und Frames Feature
Beschreibung
Browser
height = number
Höhe
width = number
Breite
left = number
Abstand vom linken Bildschirmrand in Pixeln
MSIE ab4
top = number
Abstand vom oberen Bildschirmrand in Pixeln
MSIE ab4
screenX† = number
Abstand vom linken Bildschirmrand in Pixeln
NN4
ScreenY† = number
Abstand vom oberen Bildschirmrand in Pixeln
NN4
innerWidth = number
innere Breite (ersetzt width)
NN4
innerHeight = number
innere Höhe (ersetzt height)
NN4
fullscreen ={ yes | no | 1 | 0 }
Vollbildmodus, ohne jegliche Leisten
MSIE ab4
dependent ={ yes | no | 1 | 0 }
abhängiges Fenster, wird geschlossen, wenn öffnendes Fenster geschlossen wird
NN4
*Achtung: die Möglichkeit, Fenster zu minimieren bzw. zu maximieren, wird damit nicht beeinflusst †Obwohl nicht dokumentiert, zeigt die Erfahrung, dass auch NN4 left und top versteht.
Sie ahnen es, auch die Übersicht der Features ist nicht vollständig. Hier fehlen aber nur wenige, die Sie in der Praxis sicher kaum vermissen werden. Freuen Sie sich statt dessen mit mir, dass die Zahl der Übereinstimmungen recht groß ist. Wenn Sie nicht positionieren wollen oder nicht gerade vorhaben, den ohnehin nur bei MSIE ohne Weiteres möglichen Vollbildmodus zu nutzen, können Sie sogar auf jegliche Unterscheidung verzichten. Auf eines sollten Sie aber unbedingt achten: Leerzeichen zwischen den Parametern sind zum Beispiel für Netscape-Browser Fehler, sie führen in der Regel dazu, dass der Browser die Interpretation an der Stelle abbricht und für alles, was danach kommt, Standardwerte verwendet. Also gewöhnen Sie sich am besten gleich an, grundsätzlich keine Leerzeichen zu schreiben. Fenster kleiner als 100x100 können Sie nicht so ohne Weiteres öffnen. Das ist eines der Sicherheitsfeatures12 der Browser. Sowohl Microsoft als auch Netscape benutzen diese Grenze.
130
Grundlagen Jetzt wissen wir endlich genug, um unser erstes Fenster zu öffnen. Es soll fensterchen heißen (sName), 400x300 Pixel groß sein, Statuszeile und Menüleiste haben und, wenn notwendig, Scrollleisten zeigen, außerdem soll der Benutzer die Grö ß e verändern können. Beginnen wir damit, den Parameterstring (sFeatures) in einer String-Variablen zusammenzubauen. So vorzugehen erweist sich meist als ein sehr praktisches Verfahren: var para = ''; para += 'width=400,height=300'; para += ',status=1,'; para += ',menubar=1'; para += ',scrollbars=1'; para += ',resizable=1'; para += ',toolbar=0'; para += ',location=0'; para += ',directories=0';
Diese Kombination halte ich persönlich übrigens für eine in vielen Fällen sinnvolle. Sie macht Vorgaben, lässt aber dem Benutzer noch genügend Freiheit, das Fenster seinen ganz persönlichen Wünschen anzupassen. Probieren Sie es ruhig aus, schauen Sie, ob Sie meine Meinung teilen. Mit dem so vorbereitenden String ist es nun eine Kleinigkeit, das Fenster selbst zu öffnen: neu = window.open('irgendwo.html','fensterchen',para);
Sinnvollerweise werden Sie die vorstehenden Anweisungen in einer Funktion unterbringen, um sie bei Bedarf aufrufen zu können. Fenster, die man geöffnet hat, kann man auch wieder schließen. window. close() erledigt das. Sie dürfen allerdings nicht jedes beliebige Fenster schließen. Bei Fenstern, die Sie selbst per JavaScript geöffnet haben, und solchen, deren History nur den gerade aktuellen Eintrag enthält, steht Ihnen nichts im Wege. Anderenfalls wird der Browser zuerst einmal dem Benutzer die Frage stellen, ob ihm das recht ist. Diese Sicherheitsmaßnahme hat ihre Berechtigung. Stellen Sie sich vor, Sie surfen durchs Web, klicken sich unbesorgt von Link zu Link, ohne sich den URL jeder einzelnen Seite einzuprägen. Das müssen Sie ja auch nicht, denn genau dafür gibt es die History, die Sie zu jeder besuchten Seite ganz einfach wieder zurückführt. Und nun gelangen Sie plötzlich auf eine Seite, deren Autor meint, das Fenster wäre überflüssig, macht es einfach zu. Wie würden Sie reagieren? Ich vermute, zu Jubelstürmen reißt Sie das nicht gerade hin. Aus dem Grund hat man diese Abfrage eingebaut, die solches nur 12.Es gibt Möglichkeiten, mit Hilfe erweiterter Privilegien einige der Einschränkungen aufzuheben, aber das ist ein eigenes umfangreiches, jedoch vergleichsweise wenig praxisrelevantes Thema. Deswegen konnte es in diesem Workshop nicht berücksichtigt werden.
131
7 Fenster und Frames mit Zustimmung des Benutzers zulässt. Existiert nur dieses eine Fenster, so dass window.close() auch gleich den gesamten Browser schließen würde, fragen sie in der Regel auch erst einmal nach.
7.1.3 window ist Eigenschaft von window
Beziehungen zwischen Fenstern und Frames
In der Liste der Eigenschaften haben Sie gelesen, dass window eine Eigenschaft von window sein kann. Wie das? Wir haben gerade eben mit neu = window.open(); ein Fenster geöffnet. Im Ergebnis ist jetzt neu eine Eigenschaft unseres Ursprungsfensters. Mit alert(self.neu);13
erkennen wir, dass es ein Objekt ist, genauer gesagt, ein window-Objekt. Damit haben wir auch gleich einen Weg gefunden, auf dessen Eigenschaften zuzugreifen: self.neu.Eigenschaft sollte uns das ermöglichen. Probieren wir es aus, bauen wir diesen Link in unsere Ursprungsdatei ein und klicken ihn nach dem Öffnen an: über neu
Sie sehen, die Eigenschaften des neuen Fensters werden Ihnen der Reihe nach genannt.14 Damit ist es nun ein Leichtes für Sie, auch eventuell in diesem Fenster selbst definierte Variablen, Objekte und Funktionen zu erreichen. Ursprungsfenster erreichen über opener
Was uns noch fehlt, ist der umgekehrte Weg. Sie wollen vom neuen Fenster auf das Ursprungsfenster zugreifen. Dafür gibt es die Eigenschaft opener. Versuchen wir jetzt also, diesen umgekehrten Weg zu gehen, schreiben wir in die Datei, die in das neue Fenster geladen wird: über neu
Es passiert, was wir erwartet haben: Wir bekommen die Eigenschaften des Ursprungsfensters nacheinander gezeigt. Haben Sie beobachtet, dass auch die Funktion oeffnen() und selbst neu dabei waren? Das war aber noch nicht alles, was es zu »window ist Eigenschaft von window« zu sagen gibt. Denken Sie bitte an Frames. Sie wissen, jeder Frame hat sein eigenes window-Objekt, gehört aber zum übergeordneten Fenster. Es sieht aus, als würde es jetzt kompliziert. Keine Sorge, es bleibt überschaubar. Man muss nur ein paar Dinge dazu wissen. 13.Voraussetzung ist natürlich, dass neu im gesamten window (global) bekannt ist, nicht etwa nur lokal innerhalb einer Funktion. 14.Komplett finden Sie das Beispiel, das wir gerade Schritt für Schritt aufgebaut haben, auf der beiliegenden CD.
132
Grundlagen Schauen wir uns ein Beispiel an, dessen Struktur Ihnen ganz sicher vertraut ist, ein Frameset mit zwei Frames. ein Frameset <noframes>
Bleiben wir zunächst auf dieser Ebene und sehen nach, welche Frames existieren. Dafür eignet sich frames[] recht gut. In dieser Collection finden wir die Frames eines Fensters wieder. Wie bereits bei anderen Collections gesehen, existiert eine Eigenschaft length, die uns die Anzahl der Elemente liefert. Da die Elemente window-Objekte sind, haben sie auch eine name-Eigenschaft. Das bedeutet, wir können den folgenden kleinen Script-Block im head unterbringen: <script language="JavaScript" type="text/javascript">
Nachdem der Inhalt geladen wurde, erfahren wir: frames.length : 2 frames[0].name : links frames[1].name : rechts
Die Werte der name-Eigenschaften resultieren also auch bei Frames aus nameAttributen, nämlich aus denen der frame-Tags. Diese frame-Tags sind es also, mit deren Hilfe im HTML-Code die window-Objekte definiert werden. Die Angabe numerischer Indizes in frames[] ist eine Möglichkeit, Frames anzusprechen. Wie bereits bei anderen Objekten gesehen, gibt es auch hier noch andere Wege. Sofort klar sind Ihnen sicher:
133
7 Fenster und Frames window.frames['links'] und window.links window.frames['rechts'] und window.rechts Wir haben weiter oben veranlasst, dass unsere Funktion zeigeFrames() nach dem Laden ausgeführt wird, indem wir mit window.onload = zeigeFrames; dem Event-Handler onload die Funktion zeigeFrames zugewiesen haben. Das ist zwar die eleganteste Art, aber Sie sollten daran denken, dass die alten Browser sie nicht kennen. Wenn Sie nicht ausschließen können, dass Ihre Skripte mit so einem Browser in Berührung kommen, verwenden Sie alternativ die Inline-Form: Auf übergeordnete Fenster zugreifen
Auch hier ist die Umkehrung interessant: Wie kann man von einem Frame aus auf das »Hauptfenster« zugreifen? Auch einen Frame von einem anderen aus zu erreichen, hatte ich Ihnen versprochen. Dafür gibt es zwei Eigenschaften des window-Objektes, die uns gute Dienste leisten: top und parent.
top und parent
top weist immer auf das Fenster der obersten Ebene. parent weist auf die nächsthöhere Ebene. In unserem Beispiel sind sie identisch, aber manchmal geht es ja auch etwas verschachtelter zu. Sind gar keine Frames vorhanden, so sind beide identisch mit window selbst. Nehmen wir an, in unserem Frame links gibt es eine Variable abc und im Frame rechts eine Funktion xyz(). Dann erreichen wir die beiden zum Beispiel so: Von Frame links aus: self.abc oder einfach nur abc parent.rechts.xyz() Von Frame rechts aus: self.xyz() oder nur xyz() parent.links.abc Lassen Sie Ihrer Phantasie freien Lauf, überlegen Sie, wie man kreuz und quer referenzieren kann, wie man vielleicht zusätzlich noch von einem Frame aus geöffnete Fenster erreicht oder sogar darin enthaltene einzelne Frames. Dass das alles tatsächlich geht, ist Ihnen sicher inzwischen klar geworden. Das Prinzip ist sehr logisch, man muss nur die Strukturen ständig im Auge behalten.
7.1.4
Inhalte dynamisch generieren
Dass Sie Teile einer Seite dynamisch mit Hilfe von JavaScript generieren können, wissen Sie bereits; document.write() haben Sie schon des Öfteren benutzt. Das ist immer dann sinnvoll, wenn es Passagen gibt, die in Abhängigkeit von konkre-
134
Grundlagen ten Umständen variieren. Die entsprechenden JavaScript-Anweisungen dafür haben Sie an geeigneter Stelle in der Datei selbst untergebracht. Was aber, wenn die statischen Teile gar nicht vorhanden sind oder nicht mehr der Rede wert sind? Haben Sie schon einmal daran gedacht, ganze Seiten dynamisch aufzubauen? Das kann recht praktisch sein, Sie sparen sich eine statische Datei, die vermutlich ohnehin kaum mehr als , und enthalten würde. Dafür muss man den HTML-Code so einer physisch gar nicht existierenden Datei natürlich von »anderswo« aus aufbauen. Irgendwo müssen die dafür erforderlichen JavaScript-Anweisungen ja stehen. Wir haben gerade gesehen, dass Fenster und Frames von anderen Fenstern oder Frames aus angesprochen werden können. Was liegt also näher, als über genau diesen Weg komplette Inhalte aufzubauen? Bevor wir das nun ausprobieren, müssen wir uns noch zwei Methoden des document-Objektes ansehen: document.open() und document.close(). document.open([mimeTyp,[replace]]) öffnet einen Ausgabe-Stream. mimeTyp gibt dabei die Art der Daten an, zum Beispiel text/html für HTMLText, text/plain für ASCII-Text. replace sorgt dafür, dass kein neuer History-Eintrag erfolgt. In aller Regel ist es nicht erforderlich, Parameter anzugeben, denn text/html ist Standard, und replace braucht man nur selten. document.close() schließt den Stream ab. So ein dynamischer Aufbau hat damit immer die folgende Struktur15: Fensterreferenz.document.open(); Fensterreferenz.document.write(); Fensterreferenz.document.close();
Bitte verwechseln Sie document.open() und document.close() nicht mit window.open() und window.close(). Probieren wir es am Beispiel einer kleinen Test-Hilfe aus, lassen wir uns die Eigenschaften eines Objektes in einem neuen Fenster auflisten. Das Skript im folgenden Beispiel ist schon fast selbsterklärend, es öffnet ein Fenster, schreibt dort hinein den Code einer kompletten HTML-Datei von bis . Der eigentliche Inhalt ist eine Liste aller Eigenschaften unseres Objektes, die über for...in ermittelt wird. Beim Aufruf der Funktion zeigeObjekt() übergeben wir ganz einfach das Objekt als String, über das wir etwas erfahren wollen.
15. document.open() wird auch gelegentlich weggelassen
135
7 Fenster und Frames zeige Objekt <script language="JavaScript" type="text/javascript">
Info
136
Grundlagen Warum habe ich zwei Funktionen benutzt, es ginge doch auch in einer? Ja, im Prinzip ist das richtig. Aber unter bestimmten Umständen kann es Ihnen passieren, dass die Zeit nicht ausreicht, die der Browser zum Öffnen des Fensters zur Verfügung hat. Sie erhalten eine Fehlermeldung, wenn Sie zu früh etwas hineinschreiben wollen. Deswegen bauen wir einen für uns kaum merklichen, für den Browser aber ausreichenden Zeitpuffer ein. Auf eines möchte ich noch hinweisen. Dieses einfache Skript hat einen ganz kleinen praktischen Haken: for...in funktioniert in einigen seltenen Fällen nicht 100%ig, NN3 zum Beispiel hat da schon ab und an einfach mal etwas weggelassen. Versuchen Sie, mit MSIE auf dem Weg eine leere Collection anzusprechen – navigator.mimeTypes ist so eine –, dann präsentiert er Ihnen eine Fehlermeldung. Für Testzwecke reicht es sicher, das zu wissen. Bei Skripten, die auf Rechnern der Besucher Ihrer Seiten laufen, sollten Sie sich allerdings Gedanken darüber machen, wie Fehler zu vermeiden, abzufangen und zu behandeln sind. Nutzen Sie das in Kapitel 3 über Browserunterschiede Gelernte und lesen Sie später in Kapitel 11, wie man Fehler abfangen und behandeln kann. Möchten Sie diese und ähnliche Funktionen parat haben, wenn es etwas zu testen gibt? Dann packen Sie sie einfach in eine externe JavaScript-Datei. Die ist bei Bedarf ganz schnell eingebunden.
Externe JavaScript-Dateien sind praktisch für Tests
Wünschen Sie sich solche Hilfsfunktionen zum Objektbaum Ihrer Seiten noch komfortabler? In Kapitel 9 bekommen Sie kundige Anleitung zum Basteln. Viel eleganter als externe JavaScript-Dateien sind für solche Zwecke übrigens Bookmarklets. Die kennen Sie nicht? Das sind JavaScript-Anweisungen, die man direkt als Bookmark im Browser speichert. Damit stehen sie auf jeder Seite zur Verfügung, ohne dass der Code direkt in die Datei eingebunden werden muss. Wollen Sie dies ausprobieren? Legen Sie sich einfach einmal ein Bookmark an, geben Sie lediglich anstelle des gewohnten URLs ein: javascript:alert('Hallo, ich bin ein Bookmarklet')
Was passiert, wenn Sie das Bookmark anwählen? Die Anweisung wird ausgeführt. Das ist das Prinzip – einfach und hilfreich. Mehr zum Thema Bookmarklets finden Sie auf der CD; schauen Sie bei Wolfgang Schwarz nach, er ist unter den Autoren dieses Buches der Spezialist für Bookmarklets.
7.1.5
Informationen weitergeben
Nicht immer reicht es aus, auf Fenster und deren Inhalte zuzugreifen, die zur gleichen Zeit geladen sind. Ab und an wünschen Sie sicher, Informationen an Seiten weiterzureichen, die erst später geladen werden. Dafür gibt es zwei grundlegende Herangehensweisen.
137
7 Fenster und Frames search- und hash-String benutzen Links, in denen zum Beispiel Anker angesprungen werden oder in denen Parameter mitgegeben werden, kennen Sie. So etwas ist Ihnen von daher geläufig: nach woanders
Wir machen uns zunutze, dass man Seiten so aufrufen kann. Das Link-Objekt stellt uns die Teile sogar als separate Eigenschaften zur Verfügung. Schauen wir mal, was es so alles gibt. Die weiter oben benutzte Funktion zeigeObjekt() kann uns auch dabei helfen. Konstruieren wir also zwei Links und sehen nach:
...#irgendwas
...?x=123&y=abc
Info zum ersten Link
Info zum zweiten Link
Je nach verwendetem Browser erhalten wir mehr oder weniger lange Listen der Eigenschaften unserer beiden Link-Objekte. Enthalten sind aber unabhängig vom Browser sicher document.links[0].hash=#irgendwas document.links[1].search=?x=123&y=abc
Folgen wir nun den beiden Links und sehen in der aufgerufenen Seite nach, ob die Werte in self.location wiederzufinden sind: self.location
Und siehe da, sie sind es16. Damit haben wir einen Weg gefunden, den wir beschreiten können, wenn Werte von Seite zu Seite weitergereicht werden sollen. window.name verwenden Die eben beschriebene Lösung funktioniert, aber sie hat auch ein paar kleine Nachteile. Zum Beispiel wird durch Anhängen eines search-Strings an den URL die Seite neu angefordert, eine vielleicht im Cache des Besuchers bereits vorhandene Kopie nicht genutzt.
16.Ausnahmen bestätigen auch hier die Regel. Unter file-Protokoll steht search nicht bei allen Browsern zur Verfügung, bekannt sind MSIE3 und MSIE4. Wahrscheinlich ist das kaum störend für Sie, denn unter http funktioniert es.
138
Grundlagen Schauen wir uns also noch eine andere Möglichkeit an, eine in meinen Augen viel elegantere. Die Idee besteht darin, window.name zu verwenden. Diese Eigenschaft existiert so lange, wie das Fenster selbst existiert. Eigentlich ganz einfach, nicht wahr? Man muss eben nur darauf kommen. Beschrieben wurde dieses Verfahren im März 1999 von Thomas Fischer in der Newsgroup de.comp.lang.javascript.
window.name
Wir benutzen wieder ein ganz einfaches Beispiel. Aus einer Select-Box soll der Besucher die Grö ß e seines Wohnortes entsprechend der Einwohnerzahl heraussuchen. Einwohnerzahlen <script language="JavaScript" type="text/javascript"> Wieviel Einwohner hat Ihr Wohnort?
Es sind mehr als <select name="einwohner"> 0 1.000 10.000 100.000 1.000.000
Die wirklich interessante Anweisung ist diese hier: window.name=pObjekt.options[pObjekt.selectedIndex].value; Der Wert von window.name wird in Abhängigkeit von der getroffenen Auswahl gesetzt. Eine andere Datei kann nun in dasselbe Fenster geladen werden, ohne dass die Information verloren geht. Versuchen wir es und greifen anschließend auf window.name zu:
139
7 Fenster und Frames Einwohnerzahlen
Aha, Sie wohnen also in einem Ort mit <script language="JavaScript" type="text/javascript"> 1000) document.write('mehr als ' + einwohner/10); if (einwohner>1000 && einwohner
142
Grundlagen } // --> auf geht's
Das Hüpfen machen wir uns einfach. Wir schreiben vier Funktionen, für jede Ecke eine. Natürlich wäre es eleganter, nur eine Funktion zu verwenden, ihr jeweils zu sagen, was gerade zu tun ist. Aber wäre das auch so schön anschaulich wie in der folgenden hopphopp.html? Hopp, hopp, hopp <script language="JavaScript" type="text/javascript">
143
7 Fenster und Frames if ((self.innerWidth) || (self.document.body && self.document.body.offsetWidth)) { breit = 130; hoch = 130; } if (!breit) return; setTimeout('schritt1()',500); } // -->
7.2
Übungen
leicht
1. Übung
Schauen Sie sich bitte die Konstellation in Abbildung 7.1 an. Sie haben ein Frameset mit drei Frames: Links, Mitte und Rechts. Aus Mitte heraus haben Sie das Fenster Neu geöffnet. Das wiederum enthält die Frames Oben und Unten. Abbildung 7.1: Übung 1 Beziehungen zwischen Fenstern und Frames
Oben
Links
Mitte
N
= eu
w
i
.o ow d n
p
( en
)
Unten
Rechts
1. Sie wollen das Fenster Neu vom Frame Unten aus schließen. Mit welcher Anweisung können Sie das erreichen? 2. Wie rufen Sie die Funktion machwas(), die sich in Links befindet, von Oben aus auf? 3. Wie ermitteln Sie von Mitte aus die Anzahl der Links in Rechts?
144
Übungen leicht
2. Übung
Wie stellen Sie fest, ob eine Datei Teil eines Framesets ist? mittel
3. Übung
Sie wollen eine kleine Galerie gleich großer Bilder präsentieren. Die Bilder sollen in einem kleinen zusätzlichen Fenster gezeigt werden. Für die Steuerung verwenden Sie bitte zwei Links: 왘 Der erste Link öffnet das Fenster und lädt eine fertige HTML-Datei, die das
erste Bild der Serie enthält. 왘 Der zweite Link sorgt dafür, dass im kleinen Fenster das vorhandene Bild
gegen das nächste ausgetauscht wird. Bitte tauschen Sie nur das Bild. Ist das letzte Bild erreicht, beginnen Sie einfach wieder von vorn. leicht
4. Übung
Welche Möglichkeiten haben Sie, Informationen von Seite zu Seite weiterzureichen? mittel
5. Übung
Eine kleine Farb-Spielerei gefällig? Lassen Sie doch mal die Hintergrundfarben für eine beliebige Anzahl von Frames zyklisch wechseln. schwer
6. Übung
Erinnern Sie sich an die Linkliste aus Kapitel 3? So richtig schön war sie noch nicht. Angewiesen darauf, für jede Form der Anzeige eine eigene Datei zu erstellen, war es nicht wirklich eine bequeme Lösung. Inzwischen wissen Sie, dass man Ausgaben auch in anderen Frames generieren kann. Das heißt, man könnte jetzt darüber nachdenken, die Liste selbst nur einmal zu laden und ganz verschiedene, sozusagen auf Knopfdruck abrufbare Anzeigefunktionen zu schreiben. 1. Wie könnte eine solche prinzipielle Lösung aussehen? Skizzieren Sie das Konzept. 2. So vorbereitet gehen wir nun daran, unsere Wünsche etwas zu präzisieren. Wir hätten gern eine ungeordnete Anzeige und eine thematisch geordnete (so ähnlich, wie wir sie bereits in Kapitel 3 realisiert haben), eine alphabetische Übersicht und eine einfache Suchfunktion.
145
7 Fenster und Frames mittel
7. Übung
Öffnen Sie ein kleines Fenster, positionieren Sie es in der linken oberen Bildschirmecke und verändern Sie die Grö ß e schrittweise, bis sie ca. 2/3 der Bildschirmbreite und -höhe beträgt. Alle Aktionen sollen vom öffnenden Fenster aus erfolgen.
7.3
Tipps
Tipps zur 3. Übung 왘 Denken Sie daran, dass Sie auf das neue Fenster und auch auf das gela-
dene Dokument darin zugreifen können. Deswegen geben Sie dem imgElement in der Datei, die das erste Bild enthält, vorsorglich einen Namen. 왘 Die Bilder selbst, besser gesagt, deren URLs, legen Sie am besten in einem
Array ab. Das brauchen Sie dann nur zu durchwandern, um die Bilder der Reihe nach zu zeigen. Tipp zur 5. Übung 왘 Die notwendigen Funktionen bringen Sie am besten in der Datei unter, die
das Frameset enthält. Von da aus erreichen Sie bequem alle Frames, können die Hintergrundfarben auslesen und zum nächsten Frame »verschieben«. Für die Hintergrundfarbe steht der Wert in document.bgColor . Tipps zur 6. Übung 왘 Denken Sie daran, dass man komplette Seiten dynamisch aufbauen kann,
und denken Sie daran, dass solche Seiten sich in einem Frame befinden können. 왘 Sehen Sie für den Frame, der später die Ausgaben enthalten soll, zunächst
eine Dummy-Datei vor. Tipp zur 7. Übung 왘 Denken Sie daran, dass Netscape- und MS-Browser Fenstergrö ß en zwar mit
Hilfe derselben Methoden verändern können, die aktuelle Breite ihrer Fenster aber auf unterschiedlichen Wegen preisgeben.
146
Lösungen
7.4
Lösungen
Lösung zu 1 1. parent.close() oder top.close() 2. parent.opener.parent.Links.machwas() 3. Anzahl_der_Links = Parent.rechts.Links.length;
Lösung zu 2 Fragen Sie nach, ob das Fenster selbst identisch ist mit dem auf der obersten Ebene in der Fensterhierarchie: if (self == top) Lösung zu 3 Das hier könnte die Datei mit den beiden Links sein: Kleine Galerie <script language="JavaScript" type="text/javascript">
147
7 Fenster und Frames para += ',scrollbars=1'; para += ',resizable=1'; para += ',toolbar=0'; para += ',location=0'; para += ',directories=0'; fensterchen = window.open('bilder.html','neu',para); fensterchen.focus(); return false; } // Bild wechseln function naechstesBild() { // existiert fensterchen und darin dasBild? if (self.fensterchen && self.fensterchen.document && self.fensterchen.document.dasBild) { index++; // Am Ende? Dann von vorn anfangen if (index >= Bilder.length) index = 0; // Bild tauschen fensterchen.document.dasBild.src=Bilder[index]; fensterchen.focus(); } } // --> Kleine Galerie
zeig mal die Bilder
nächstes Bild
Die Datei bilder.html könnte so aussehen: Bilder
148
Lösungen
Lösung zu 4 Es gibt verschiedene Möglichkeiten. Zuerst ist die Frage zu stellen, ob die Seiten, zwischen denen Informationen auszutauschen sind, gleichzeitig geladen sind. Ist das mit Sicherheit der Fall, dann ist das Problem schon so gut wie gelöst. Wenn sich die Fenster untereinander »kennen«, können sie auch untereinander auf ihre Inhalte zugreifen. Sollen Informationen im Wortsinn »weitergegeben« werden, also wirklich später verfügbar sein, dann bieten sich an: search- bzw. hash-String und window.name. Lösung zu 5 Ich habe hier einmal 10 Frames benutzt17 und ganz einfach in jeden Frame dieselbe Datei geladen, es kam ja nicht auf Inhalte an, und nachträglich die Hintergrundfarben in der Funktion initia() gesetzt. Danach geht es los. faerben() wird ein wenig zeitverzögert aufgerufen, die Variable lauf zeigt an, dass der Wechsel läuft. Famelei <script language="JavaScript" type="text/javascript">
149
7 Fenster und Frames setTimeout('faerben()',100); } // solange lauf wahr ist, werden die Hintergrundfarben // versetzt function faerben() { var i = parent.frames.length-1; // letzte Farbe in der Kette merken var Farbe = parent.frames[i].document.bgColor while (i>0) { parent.frames[i].document.bgColor = parent.frames[i-1].document.bgColor; i--; } parent.frames[0].document.bgColor=Farbe; if (lauf) setTimeout('faerben()',500); } // --> <noframes>
In eine oder mehrere Dateien in den Einzelframes bauen wir noch eine Möglichkeit ein, das Spielchen zu stoppen. Was ist zu tun? Nicht viel. Das window mit dem Frameset enthält eine Variable lauf, nur wenn diese auf true steht, wird der nächste Farbwechselzyklus angestoßen. Also setzen wir die einfach auf false. Unsere kleine Beispieldatei farbig.html enthält dafür eine Funktion anhalten() und einen Button, mit dem sich die Funktion aufrufen lässt. Fehler vermeiden, wenn Frames fehlen
150
Weil es passieren kann, dass Dateien, die eigentlich in einen Frame gehören, auch mal einzeln geladen werden, sorgen wir dafür, dass das Fehlen von Funktionen oder Variablen in anderen Frames nicht zu lästigen Fehlern führt. Eine
Lösungen einfache Abfrage auf Existenz reicht in der Regel aus; ist irgendetwas nicht vorhanden, dann soll auch nichts passieren. Hier ist es die Variable lauf, die wir benötigen, deswegen fragen wir nach mit if(parent.lauf). Achtung! So etwas hat auch seine Tücken. Ist Ihnen aufgefallen, dass die Abfrage auch dann false liefert, wenn lauf zwar existiert, aber eben den Wert false hat? In dieser Übung macht das nichts, denn wir tun ohnehin nichts anderes als den Wert auf false zu setzen. Aber Sie sehen, man sollte kurz darüber nachdenken. <script language="JavaScript" type="text/javascript">
Lösung 6 Prinzip der Lösung:
Bauen wir ein Frameset mit zwei Frames. Die JavaScript-Routinen und die Daten, also das Array mit unseren Links, bringen wir am besten im Fenster der obersten Ebene unter, d.h., wir müssen sie in die Datei einbauen, in der das Frameset definiert wird. Das lässt uns die Freiheit, Frames nach Wunsch einfach zu ändern, ohne dass etwas verloren geht. Natürlich verwenden wir dafür externe JavaScript-Dateien. In einem der Frames können wir die »Knöpfe« unterbringen, auf die man nur noch drücken muss – na ja, Buttons tun es sicher auch. Der andere Frame wird die jeweils dynamisch generierte Anzeige enthalten. Ein paar zusätzliche Methoden für verschiedene Anzeigevarianten sind nach den Übungen in Kapitel 3 sicher keine Schwierigkeit mehr. Die Lösung selbst
Beginnen wir mit den Routinen zum Aufbauen der Daten und zum Ausgeben. Die packen wir alle zusammen in eine JavaScript-Datei, meine heißt
151
7 Fenster und Frames bookmark_routinen.js. Die Konstruktor-Funktion und ein paar Methoden sind Ihnen so neu nicht, sie ähneln denen, die wir bereits früher verwendet haben. So richtig neu sind die beiden Funktionen zeigeAlphabetisch() und such(). Ich werde darauf jetzt aber nicht so sehr eingehen, denn die Routinen sind nicht kompliziert, Sie verstehen sie sicher auch ohne weitere Erklärung. Für die Sortierung habe ich ein ganz einfaches Verfahren gewählt, das für diesen Zweck jedoch vollkommen ausreichend ist. Vielleicht fällt Ihnen auf, dass ich die Funktionen gar nicht als Methoden zugewiesen habe. Dann sehen Sie sich bitte die Datei mit den »Knöpfen« an. Da immer nur eine der Methoden gebraucht wird, habe ich kurz entschlossen auch nur eine im Objekt vorgesehen, nämlich zeigeEintrag. Aktuell ändere ich lediglich deren Referenz, weise die jeweils passende Funktion zu. // Konstruktorfunktion fuer Links function macheLink(pTitel, pAdresse, pAutor, pThema, pAnmerkung, pScreenshot) { this.Titel = pTitel; this.Adresse = pAdresse; this.Autor = pAutor; this.Thema = pThema; this.Anmerkung = pAnmerkung; if (pScreenshot.length > 0) this.Screenshot = pScreenshot; else this.Screenshot = 'keinbild.gif'; this.erledigt = false; this.zeigeEintrag = zeigeEintrag; } // fuegt als Methode einer Liste von Links neue hinzu function hinzufuegen(pTitel, pAdresse, pAutor, pThema, pAnmerkung, pScreenshot) { this[this.length] = new macheLink(pTitel, pAdresse, pAutor, pThema, pAnmerkung, pScreenshot); } // bereitet Ausgabe fuer einen einzelnen Eintrag vor // wird von allen anderen Ausgabefunktionen benutzt function zeigeEintrag() { var Aus = ''; Aus += ''; Aus += ''; Aus += ''; Aus += ''; this.Titel + ''; '' + this.Anmerkung + ''; '' + this.Autor + ''; ''; Aus;
}
Die folgende Funktion wird aufgerufen, wenn das komplette Frameset geladen ist, d.h., die Objekte werden angelegt, die Liste kann »mit Daten gefüllt« werden. Die Daten holen wir aus einer anderen Datei, das ist übersichtlicher und änderungsfreundlicher. Bei mir heißt sie bookmark_eintraege.js. Wir sehen sie gleich im Anschluss. /* * * legt Liste an * * */ function macheLinkliste() { // erst mal nachsehen, ob der Browser Arrays kennt if (window.Array) { // eine "Liste" fuer alle Links anlegen meineLinks = new Array(); // Methode fuer das Hinzufuegen definieren meineLinks.hinzufuegen = hinzufuegen; // Vorbereitungen sind abgeschlossen, // Daten koennen kommen Bookmarks_anlegen(); } } /* * * verschiedene Anzeigevarianten * * */ // zeigt ungeordnet function zeigeUngeordnet(wohin) { for (var i=0; i this[i+1].Titel) { fertig = false; Zw = this[i]; this[i] = this[i+1]; this[i+1] = Zw; } } } for (var i=0; i -1) || (this[i].Anmerkung.toLowerCase().indexOf(wonach) > -1)) wohin.document.writeln(this[i].zeigeEintrag()); }
154
Lösungen Datei bookmark_eintraege.js (auszugsweise): function Bookmarks_anlegen() { // einzelne Links eintragen with (meineLinks) { hinzufuegen('JavaScript-Informationen', 'http://mintert.com/javascript/', 'S. Mintert', 'JavaScript', 'Buch, Chronologie von JavaScript, Standards etc.', 'javascriptmintert.gif'); hinzufuegen('JavaScript-Notizen', 'http://netz-notizen.de/javascript', 'Ch. Kühnel', 'JavaScript', 'Beispiele, Tipps zu JavaScript und DHTML', 'javascriptnotizen.gif'); hinzufuegen('HTML 4', 'http://www.w3.org/TR/html4/', 'W3C', 'HTML', 'Spezifikation HTML 4', ''); } }
In der Frameset-Deklaration versorgen wir die beiden Frames mit Namen, dann können wir sie später leichter ansprechen. Nachdem alles geladen ist, müssen wir lediglich den Aufbau der Linkliste anstoßen, indem wir die Funktion macheLinkliste() aufrufen. Bis zu dem Augenblick sollten wir unbedingt damit warten, um sicher zu sein, dass wirklich alles da ist, was wir brauchen. Linkliste <script language="JavaScript" src="bookmark_routinen.js" type="text/javascript"> <script language="JavaScript" src="bookmark_eintraege.js" type="text/javascript"> <noframes>
155
7 Fenster und Frames Was uns jetzt noch fehlt, sind lediglich die Aufrufe. Die finden wir in zu_linkliste_aufrufe.html. Hier merken wir uns geschickter Weise das Ausgabeziel in einer Variablen (wohin). Damit geben wir den Zielframe nur an dieser einen Stelle an, sind also recht flexibel, sollte uns irgendwann dieser Frame nicht mehr so recht gefallen für diesen Zweck. Alles andere ist sicher selbsterklärend. Doch nicht? Probieren Sie die Lösung von der CD! Anschauung ist auch keine schlechte Lehrmeisterin. <script language="JavaScript" type="text/javascript"> Meine Links
156
Lösungen
(in Thema, Autor und Anmerkung)
Lösung zu 7 Das ist eine mögliche Lösung: Immer groesser <script language="JavaScript" type="text/javascript">
Die Funktion zur Fehlerbehandlung fängt Fehler ab. An sich dürften keine auftreten, wenn man grundsätzlich dafür sorgt, dass auf das Fenster Neu nur zugegriffen wird, wenn es auch da ist, also auch dann nicht, wenn es zwischenzeitlich von Hand geschlossen wurde. Darüber sollte die closed-Eigenschaft Auskunft geben. Das ist die Theorie, bei Netscape-Browsern funktioniert sie auch, aber MSIEs sind an der Stelle etwas bugbehaftet. Deswegen habe ich mich hier entschlossen, etwaige Fehlermeldungen einfach zu unterdrücken. function machauf() { var para = ''; para += 'width=100,height=100'; para += ',status=0,menubar=0'; para += ',scrollbars=0,resizable=1'; para += ',toolbar=0,location=0'; para += ',directories=0'; // positionieren links oben para += ',left=0'; para += ',top=0'; Neu = window.open('waechst.html','',para); // kleine Verzögerung, dann beginnt das Wachstum setTimeout('los()',80); } function los() { 157
7 Fenster und Frames // gar kein Fenster da? if (!self.Neu) return; // kein screen-Objekt, dann ist Browser zu alt if (!self.screen) return; // initialisieren var horizontal = 0; var vertikal = 0; var Neu_breit = 0; var Neu_hoch = 0; var breit = screen.width/3*2; var hoch = screen.height/3*2; if (Neu && Neu.innerWidth) { // Netscape var Neu_breit = Neu.innerWidth; var Neu_hoch = Neu.innerHeight; } else if (Neu && Neu.document.body && Neu.document.body.offsetWidth) { // MSIE var Neu_breit = Neu.document.body.offsetWidth; var Neu_hoch = Neu.document.body.offsetHeight; } if (!Neu_breit) return; if (Neu_breit < breit) horizontal = 10; if (Neu_hoch < hoch) vertikal = 10; Neu.resizeBy(horizontal,vertikal); setTimeout('los()',80); } // -->
machauf
Die Datei, die in das kleine Fenster geladen wird, ist nicht interessant. Sie spielt selbst keine Rolle.
158
8
Cookies von Wolfgang Schwarz,
[email protected] 8.1
Grundlagen
Cookies (Kekse) sind kleine Informationshäppchen, die eine Webseite auf dem Rechner des Anwenders ablegen und später wieder auslesen kann. In diesem Kapitel erfahren Sie, wie man Cookies setzt, liest und ihren Verfallszeitpunkt festlegt. Nebenbei lernen Sie noch etwas über das nützliche Date-Objekt. Die Geschichte der HTTP Cookies gehört in das große Buch der unerklärlichen psychologischen Massenphänomene, von denen die Menschheit hin und wieder heimgesucht wird. Seit ihrer Einführung durch Netscape 2 ranken sich die bizarrsten Gerüchte um die kleinen Kekse: angefangen vom Ausspionieren privater Internetgewohnheiten bis zu Angriffen auf die Windows-Registry soll mit Cookies angeblich alles möglich sein.
Cookie-Paranoia
In Wirklichkeit kann man, von gelegentlichen Browserbugs einmal abgesehen, aus Cookies nur lesen, was man selbst vorher hineingeschrieben hat, und hineinschreiben kann man nur kleine Textstücke von maximal 4 KB Größe (das sind 4000 Zeichen). Viele Online-Shops verwenden Cookies als Einkaufskorb, in dem sie festhalten, welche Artikel bereits ausgewählt wurden. Andere Seiten legen in Cookies Benutzereinstellungen ab, damit diese nicht bei jedem Besuch neu festgelegt werden müssen. Cookies lesen und schreiben Mit JavaScript finden wir die Cookies einer Seite in der Eigenschaft cookie des Objekts document:
document.cookie
document.cookie
Wenn Sie diesen Wert auslesen, erhalten Sie einen String, in dem alle für die Seite zugänglichen Cookies, durch Semikolons getrennt, als Name=Wert-Paare abgelegt sind. Ich habe zum Beispiel eben auf www.yahoo.com die Anweisung javascript:alert(document.cookie)
in die Location-Zeile meines Browsers geschrieben und diese Ausgabe bekommen: B=dmu8veh9mph4t
Interessant, nicht wahr?
159
8 Cookies Pro Domain dürfen bis zu 20 Cookies gesetzt werden. Um einen Cookie zu setzen, weist man dem cookie-Objekt einfach ein neues Name=Wert-Paar zu. Wollen Sie also beispielsweise die Information speichern, dass B derzeit dmu8veh9mph4t ist, dann schreiben Sie: document.cookie = "B=dmu8veh9mph4t";
Der Wert sollte möglichst keine Leerzeichen, Semikolons, Kommata und dergleichen enthalten. Wenn Sie solche Zeichen verwenden wollen, kodieren Sie den String mit der Funktion escape(StringMitSonderzeichen). Nach dem Auslesen erhalten Sie mit unescape(CookieString) wieder die ursprüngliche Zeichenkette. Wie Sie sehen, ist Lesen und Schreiben bei Cookies nicht symmetrisch: Schreiben können Sie immer nur einzelne Name=Wert-Paare, beim Lesen erhalten Sie aber alle Paare, die im Moment für Ihre Seite gelten. Das sind gewöhnlich alle diejenigen, die die Seite selbst oder eine in der Verzeichnishierarchie darüber liegende Seite gesetzt hat. Cookie-Parameter
Wenn Sie einen Cookie schreiben, können Sie festlegen, für welche Seiten er gelten soll, das heißt, von wo aus auf ihn zugegriffen werden darf. Dazu gibt es die Parameter path und domain: document.cookie = "Name=Wert; path=Verzeichnis; domain=Domain"
Lassen Sie diese Angaben weg, wird automatisch als path das aktuelle Verzeichnis und als domain der Hostname Ihres Servers eingetragen. Ein Beispiel: Angenommen, Sie sind Webmaster von www.greenpeace.de und setzen auf der Seite www.greenpeace.de/start.html einen Cookie mit dem Inhalt »Lieblingszahl=249.95«. Später schreiben Sie auf www.greenpeace.de/hier/dort/spaeter.html noch einen, sagen wir: »Lieblingssenf= Dijon«. Jetzt liefert document.cookie auf der Seite spaeter.html: "Lieblingszahl=249.95; Lieblingssenf=Dijon«
auf start.html aber nur: »Lieblingszahl=249.95« Um den zweiten Cookie auch auf start.html lesbar zu machen, müssen Sie beim Schreiben die path-Angabe benutzen: document.cookie = "Lieblingssenf=Dijon; path=/"
Damit der Cookie auch von Ihren anderen Servern aus gelesen werden kann, zum Beispiel von www2.greenpeace.de, verwenden Sie domain: document.cookie = "Lieblingssenf=Dijon; path=/; domain=greenpeace.de"
160
Grundlagen Aus Sicherheitsgründen können Sie übrigens nur Ihre eigene Domain angeben. Neben path und domain gibt es noch zwei weitere Attribute, die Sie beim Schreiben eines Cookies festlegen können (lesen kann man diese Parameter übrigens nicht): expires und secure. Die Angabe von secure sorgt dafür, dass der Cookie nur über sichere Verbindungen (SSL) gelesen werden darf. Der expires-Wert bestimmt den Verfallszeitpunkt des Cookies. Er sollte im Format GMT (Greenwich Mean Time) bzw. UTC (Universal Time Coordinated, der neue Name für GMT) stehen. Eine solche Zeitangabe sieht zum Beispiel so aus: Tue, 07 Nov 2084 12:00:00 GMT
Ohne eine expires-Angabe wird der Cookie beim nächsten Schließen des Browsers gelöscht. Sie können einen Cookie sofort löschen, indem Sie ihm ein in der Vergangenheit liegendes Verfallsdatum geben. Zeiten in JavaScript: Das Date-Objekt Für den Umgang mit Zeiten stellt JavaScript einen eigenen Objekttyp zur Verfügung: Date. Eine Instanz davon erzeugen Sie mit var datum = new Date();
Jede Date-Instanz repräsentiert einen bestimmten Zeitpunkt. Wenn Sie wie im obigen Beispiel keine Parameter übergeben, steht das Objekt für die Zeit, an der es erzeugt wurde. Um einen anderen Zeitpunkt darzustellen, übergeben Sie einfach das entsprechende Datum an den Konstruktor. Sie können es entweder als Zahl der Millisekunden seit dem 1. Januar 1970, 00:00 Uhr GMT angeben:
Ein Datum erzeugen
var datum = new Date(Millisekunden)
oder, wenn Sie diesen Wert gerade nicht parat haben, in etwas gewohnterer Form: var datum = new Date(Jahr, Monat, Tag, Stunden, Minuten, Sekunden, Millisekunden)
Alle diese Werte sind vom Typ Integer. Die Angabe von Stunden, Minuten, Sekunden und Millisekunden ist optional. Beachten Sie, dass die Zählung der Monate bei 0 beginnt. 0 steht also für Januar, 11 für Dezember. Wir erhalten deshalb mit var mauerfall = new Date(1989, 10, 9)
ein Objekt, das den 9. 11. 1989 repräsentiert.
161
8 Cookies Methoden von Date
Das Date-Objekt verfügt über einige recht praktische Methoden. So liefert mauerfall.getDate()
den Tag des mauerfall-Datums, also 9. Die anderen Werte bekommen Sie mit getMilliseconds(), getSeconds(), getMinutes(), getHours(), getMonth() (beim mauerfall also 10), getYear() und getDay(). Letzteres liefert den Wochentag in Form einer Zahl zwischen 0 (für Sonntag) und 6 (für Samstag). Die Implementation von getYear() ist leider bei den Browserherstellern etwas verunglückt: Internet Explorer 3, Netscape 4 und Netscape 6 liefern hier die Jahreszahl gerechnet ab 1900, also beispielsweise den Wert 102 für das Jahr 2002. Navigator 2 und 3 und Internet Explorer 4 und 5 geben dagegen für Jahre ab 2000 die vierstellige Jahreszahl zurück. Werte vor 1970 können ältere Browser überhaupt nicht darstellen. Seit JavaScript1.3 gibt es getFullYear(), was in jedem Fall die vollständige Jahreszahl zurückgeben sollte. Für die Arbeit mit Cookies sind vor allem die Methoden getTime(), setTime() und toGMTString() interessant. getTime() liefert die verstrichenen Millisekunden zwischen dem 1. Januar 1970 und dem repräsentierten Datum. Mit setTime() können Sie ein Date-Objekt im Nachhinein auf eine andere Zeit stellen. toGMTString() (seit JavaScript1.3 auch toUTCString()) schließlich gibt die Zeit im GMT- bzw. UTC-Format zurück. (Sollte es jedenfalls. Die MacIntosh-Versionen von Netscape verrechnen sich dabei jedoch um einen Tag: Sie sind immer 24 Stunden voraus.) In den Übungen werden Sie sehen, wie man die Date-Methoden für die Arbeit mit Cookies einsetzt. Alternativen zu Cookies Die Beschränkungen von Cookies liegen auf der Hand: Sie können nur relativ wenig Informationen in einem recht unflexiblen Format speichern. Wegen der teilweise fehlerhaften Umsetzung in den Browsern sollten außerdem empfindliche Daten am besten gar nicht in Cookies abgelegt werden. Dazu kommt, dass viele Leute aus Angst vor der Cookie-Attacke die entsprechenden Funktionen ihres Browsers kurzerhand abgeschaltet haben. Die sicherste Alternative zu Cookies ist die Daten serverseitig mit CGI zu speichern. Wenn Sie nur Informationen von einer Seite auf die nächste übermitteln wollen, können Sie sie auch, wie in Kapitel 7 beschrieben, als search-Parameter an den URL der aufgerufenen Seite hängen − zum Beispiel: location.href="naechsteSeite.html?B=dmu8veh9mph4t";
Auf naechsteSeite.html finden Sie dann unter window.location.search
den übergebenen String »?B=dmu8veh9mph4t«.
162
Übungen Oder Sie legen die Daten im name-Attribut des window fest. Hier bleiben sie so lange bestehen, bis der Name explizit geändert oder das Fenster geschlossen wird. Internet Explorer bietet außerdem seit Version 5 spezielle Persistenz-Techniken, die auf DHTML-Behaviors beruhen, auf die wir aber hier nicht eingehen können.
8.2
Übungen
leicht
1. Übung
Erstellen Sie eine Seite mit zwei Links. Wenn man auf den ersten Link klickt, soll ein Cookie mit dem Inhalt »Nachtisch=Kekse« gesetzt werden. Ein Klick auf den zweiten Link soll den Cookie-Inhalt in einem alert-Dialog ausgeben. Der Cookie muss nicht für längere Zeit gespeichert werden. leicht
2. Übung
Wenn Sie zwei Cookies mit demselben Namen anlegen, überschreibt der zweite den ersten. Löschen Sie den Cookie von Übung 1, indem Sie ihn nochmal setzen, diesmal aber mit einem vergangenen expires-Attribut. mittel
3. Übung
Schreiben Sie eine Funktion, die überprüft, ob der Besucher Ihrer Seite Cookies aktiviert hat. mittel
4. Übung
Einige Browser erlauben die (recht unbequeme) Option, dass jeder Versuch, einen Cookie zu setzen, eigens bestätigt werden muss. Ändern Sie die Cookietest-Funktion von Übung 3, so dass sie einen von drei Werten zurückliefert: 0 wenn Cookies deaktiviert sind, 1 wenn sie aktiviert sind und nicht bestätigt werden müssen, 2 wenn jedesmal eine Bestätigung nötig ist. mittel
5. Übung
Wie Sie wissen, liefert document.cookie oft nicht nur einen Cookie, sondern eine Liste aller Cookies, die für die Seite zugänglich sind. Schreiben Sie eine allgemein verwendbare Funktion liesCookie(), die diese Liste nach einem bestimmten Cookie durchsucht. Der Name des Cookies wird als Argument übergeben, die Funktion liefert dann den dazugehörigen Wert zurück.
163
8 Cookies mittel
6. Übung
Als Nächstes schreiben Sie eine allgemein verwendbare Funktion zum Setzen eines Cookies. Dieser Funktion werden bis zu sechs Parameter übergeben: Name, Wert, Verfallsdatum, Pfad, Domain und secure-Einstellung. Versuchen Sie auch, den MacIntosh-Bug des Netscape Navigators zu berücksichtigen: Legen Sie dazu beispielsweise mit new Date(1970,0,1,12); ein Date-Objekt an, das den Mittag des 1. 1. 1970 repräsentiert. Bei Netscape auf MacIntosh enthält dieses Datum, ins GMT-Format übertragen, eine »02«. Fragen Sie das ab und korrigieren Sie entsprechend das Datum. leicht
7. Übung
Auf einer Internetseite sollen die Besucher persönlich begrüßt werden. Dazu werden sie, wenn Cookies aktiviert sind, beim ersten Besuch nach ihrem Namen gefragt. Dieser wird in einem Cookie gespeichert und bei jedem Betreten der Seite ausgelesen und angezeigt. Erstellen Sie diese Seite. Verwenden Sie dabei die Funktionen cookie Aktiviert(), liesCookie() und schreibCookie() aus den Übungen 3, 6 und 7. schwer
8. Übung
Zum Schluss noch ein sinnvolles Anwendungsbeispiel. Schreiben Sie eine Seite, die den Besucher über alle Änderungen informiert, die sich dort seit seinem letzten Besuch zugetragen haben. Das Datum des letzten Besuchs halten Sie in einem Cookie fest − aber nur, wenn der Besucher es wünscht. Das bedeutet: Ist noch kein Cookie gesetzt, wird lediglich auf die Möglichkeit hingewiesen. Über einen Link kann der Besucher dann selbst den Cookie anlegen, wenn er möchte.
8.3
Tipps
Tipp zu 3: 왘 Setzen Sie einen Test-Cookie und versuchen Sie ihn anschließend auszule-
sen.
164
Tipps Tipps zu 4: 왘 Das Bestätigen oder Ablehnen eines Cookies nimmt eine gewisse Zeit in
Anspruch. Während dieser Zeit wird die Abarbeitung des aktuellen JavaScript-Blocks angehalten. 왘 Messen Sie die Zeit vor dem Setzen des Cookies und die Zeit nach dem Set-
zen. Errechnen Sie die Differenz (in Millisekunden) zwischen den beiden Zeiten. Daraus können Sie schließen, ob in der Zwischenzeit ein Dialogfenster erschienen ist oder nicht. Tipps zu 5: 왘 Achten Sie darauf, dass Ihre Funktion nicht in besonderen Fällen versagt.
Denken Sie beispielsweise daran, dass ein Name Teil eines anderen Namens sein kann (z.B. »mail« und »email«). 왘 Durchsuchen Sie document.cookie mit der indexOf-Methode nach der Zei-
chenkette »; übergebenerName=«. Wenn dieser String nicht im Cookie vorkommt, sehen Sie nach, ob der gesuchte Cookie vielleicht der erste in der Liste ist und deshalb kein Semikolon am Anfang hat. Wenn Sie den Namen gefunden haben, bestimmen Sie von dort aus wieder mit der indexOfMethode den dazugehörigen Wert. Tipps zu 8: 왘 Speichern Sie im Cookie die Zahl der Millisekunden seit dem letzten
Besuch. Wie könnten Sie die Seitenänderungen ablegen, so dass sie sich damit einfach nach neuen Einträgen durchsuchen lassen? 왘 Ein solcher Array würde sich anbieten:
aenderungen = new Array( new Date(2000,07,10), "- Hintergrundfarbe jetzt rot", new Date(2000,09,15), "- neue Bilder von der Katze", new Date(2001,00,05), "- Hintergrundfarbe wieder grün" );
Sie brauchen dann nur in Zweierschritten diesen Array durchgehen und jeweils den Eintrag, der das Datum der Änderung enthält (in Millisekunden seit 1970), mit dem Cookie-Datum vergleichen. 왘 Der Cookie-Wert ist ein String. Die Konstruktorfunktion Date() erwartet
aber keinen String, sondern eine Zahl. Sie müssen den Wert also umwandeln, zum Beispiel indem Sie ihn mit 1 multiplizieren. 왘 Zum Lesen und Schreiben der Cookies nehmen Sie wieder die Funktionen
schreibCookie() und liesCookie().
165
8 Cookies
8.4
Lösungen
Lösung zu 1: setzen
lesen
Lösung zu 2: Wir erzeugen als erstes irgendein Datum in der Vergangenheit: var einst = new Date(1984, 0, 1);
formatieren es als GMT-String: var einstGMT = einst.toGMTString();
und hängen es als expires-Wert an den Cookie: document.cookie = "Nachtisch=irgendwas; expires="+einstGMT;
Lösung zu 3: function cookieAktiviert() { // Test-Cookie setzen document.cookie = "testCookie=bla"; // Cookie lesen: var keks = document.cookie // Wenn keks nicht undefiniert ist und "testCookie" // enthält, wurde der Cookie erfolgreich gesetzt: if (keks && keks.indexOf("testCookie") > -1) return true; // andernfalls werden Cookies nicht unterstützt: else return false; } // ein Test: alert(cookieAktiviert());
166
Lösungen Ab Version 4 des Internet Explorers und Version 6 von Netscape steht mit navigator.cookieEnabled auch eine vordefinierte Eigenschaft zur Verfügung, die angibt, ob Cookies aktiviert sind. Lösung zu 4: function cookieAktiviert() { // Start-Zeitpunkt festhalten var start = new Date(); // Test-Cookie setzen document.cookie = "testCookie=test"; // Die vergangene Zeit messen var jetzt = new Date(); var dauer = jetzt.getTime()-start.getTime(); // Wenn mehr als 500 ms vergangen sind, wurde ein Dialog // eingeblendet if (dauer > 500) { return 2; } else { // Cookie lesen var keks = document.cookie if (keks && keks.indexOf("testCookie") > -1) return 1; else return 0; } }
Lösung zu 5: function liesCookie(name) { var keks = document.cookie; // Anfangsposition des Name=Wert-Paars suchen var posName = keks.indexOf("; " + name + "="); if (posName == -1) { // vielleicht war's der erste Name in der Liste? if (keks.indexOf(name + "=") == 0) posName = 0; // nein? dann abbrechen mit Rückgabewert null else return null; } // Anfangs- und Endposition des Werts suchen var wertAnfang = keks.indexOf("=", posName)+1; var wertEnde = keks.indexOf(";", posName+1); if (wertEnde == -1) wertEnde = keks.length;
167
8 Cookies // Wert auslesen und zurückgeben var wert = keks.substring(wertAnfang, wertEnde); return unescape(wert); }
Lösung zu 6: // Diese Funktion korrigiert den Datums-Bug von // Netscape/Mac und liefert den korrekten GMTString: function fixedGMTString(datum){ var damals=new Date(1970,0,1,12); if (damals.toGMTString().indexOf("02")>0) { datum.setTime(datum.getTime()-1000*60*60*24); } return datum.toGMTString(); } function schreibCookie(name,wert,verfall,pfad,dom,secure) { neuerKeks = name + "=" + escape(wert); if (verfall) neuerKeks += "; expires=" + fixedGMTString(verfall); if (pfad) neuerKeks += "; path=" + path; if (dom) neuerKeks += "; domain=" + dom; if (secure) neuerKeks += "; secure"; document.cookie = neuerKeks; }
Lösung zu 7: Für die Ein- und Ausgabe des Besuchernamens gibt es verschiedene Wege. Wir wählen, besonders unelegant, prompt zur Eingabe und alert zur Ausgabe. ... <script language="JavaScript">
168
Lösungen besucherName = prompt("Guten Tag. Wie ist Ihr Name?"); // Verfallszeitpunkt nach 365 Tagen bestimmen var jetzt = new Date(); var verfall = new Date(jetzt.getTime() + 1000*60*60*24*365); schreibCookie("Besuchername", besucherName, verfall) } alert("Hallo " + besucherName + "!"); } // --> ...
Überlegen Sie sich gut, ob Sie so etwas wirklich verwenden wollen. Erfahrungsgemäß sind Menschen nicht besonders glücklich darüber, wildfremden Seiten erst ihren Namen sagen zu müssen, bevor sie etwas zu sehen bekommen. Ich trage zum Beispiel in solchen Fällen nur schnell irgendwelche Buchstaben ein − bei jedem weiteren Besuch werde ich dann mit »Hallo asdfjk!« begrüßt. Lösung zu 8: Dieses Skript muss im body der Seite stehen, weil es document.write verwendet, um dynamisch ausgewählten Text zu schreiben. // Der Array mit den Änderungen aenderungen = new Array( new Date(2000,07,10), "- Hintergrundfarbe jetzt rot", new Date(2000,09,15), "- neue Bilder von der Katze", new Date(2001,00,05), "- Hintergrundfarbe wieder grün" ); function liesCookie(name) { // siehe Übung 6 } function schreibCookie(name,wert,verfall,pfad,dom,secure) { // siehe Übung 7 } // Cookie lesen var warda = liesCookie("letzterBesuch"); if (warda) { // Wenn der Cookie da ist, das entsprechende Date-Objekt // erzeugen und die Liste der Änderungen seit diesem // Datum schreiben: wardaDatum = new Date(warda*1);
169
8 Cookies var str = ''; for (i=0; i
Die Kommentare dienen wie bei script-Elementen dazu, den Inhalt vor alten Browsern zu verstecken. Ich werde sie, um nicht unnötig Bäume zu verschwenden, in den hier abgedruckten Beispielen jedoch weglassen. Schauen Sie sich jetzt die CSS-Angabe an: Sie beginnt mit dem Tag-Namen des Ziel-Elements (h1), dann folgt in geschweiften Klammern die Eigenschaft, die wir für das Element festlegen wollen. Mehrere Angaben werden einfach durch Semikolons getrennt: h1 { color:blue; font-size:16pt; background-color:#ffff33 }
172
Grundlagen Diese Definition wirkt sich auf alle h1-Elemente des Dokuments aus. Für den Einsatz in DHTML werden wir aber häufig ein HTML-Element einzeln mit CSS formatieren wollen. Auch das ist möglich. Dazu geben wir dem Element im Start-Tag eine ID: irgend ein Inhalt
Über diese ID können wir uns dann im style-Bereich auf das Element beziehen: #irgendEinName { font-weight:bold }
Der ID wird also ein # vorangestellt. IDs dürfen bekanntlich nicht mehrfach vergeben werden. Soll eine Angabe für zwei oder mehr Elemente gelten, verwenden Sie stattdessen den class-Selektor. Dieser beginnt mit einem Punkt: .sehrSchoen { color:green; background-color:red }
Zwei Elemente dieser Klasse könnten etwa so aussehen: sehr schöne Überschrift
sehr schöner Absatz
Style Sheets einbinden Es gibt verschiedene Möglichkeiten, Style Sheets in eine HTML-Seite einzubinden. Eine davon haben Sie schon kennen gelernt: das Element style im Kopf der Seite. Diese Technik ist besonders geeignet für Angaben, die sich lediglich auf ein einzelnes Dokument beziehen.
Externe und interne Styles
Soll eine CSS-Definition dagegen nicht nur für eine Seite, sondern vielleicht für einen ganzen Webauftritt gelten, dann können Sie sie in einer externen Datei ablegen. Speichern Sie dazu die CSS-Angaben als Text-Datei mit der Endung css. Auf Ihren Seiten binden Sie diese dann mit dem link-Tag ein:
Style Sheets, die sich nur auf ein einziges Element beziehen, können noch auf eine andere Weise definiert werden, indem man nämlich direkt in den StartTag des Elements ein style-Attribut einfügt:
sehr schön
CSS-Angaben Die aktuelle CSS-Spezifikation des W3C, im Internet unter http://www.w3. org/TR/REC-CSS2/ zu finden, enthält 122 CSS-Eigenschaften. Tabelle 9.1 fasst diejenigen zusammen, die es sich lohnt auswendig zu lernen.
CSS-Eigenschaften
173
9 Drei Einführungen in DHTML Tab. 9.1: Ein paar mit Style Sheets definierbare Eigenschaften
CSS-Eigenschaft
Beispiel
Erklärung
color
color: #990000
die Schriftfarbe des Elements
background-color
background-color: red
die Hintergrundfarbe des Elements
font-family
font-family: Arial
die zu verwendende Schriftart
font-size
font-size: 12pt
die Schriftgröße
text-align
text-align: center
die horizontale Ausrichtung des Element-Inhalts
width
width: 10cm
die Breite des Elements
height
height: 200px
die Höhe des Elements
margin
margin: 2mm
der Abstand des Elements zu seinen Nachbar-Elementen
padding
padding: 10px
der Innenabstand zwischen ElementGrenzen und -Inhalt
Bei vielen CSS-Attributen muss eine Maßeinheit gewählt werden. Größenangaben z.B. für Abstände oder Schriften können in den Einheiten pt, pc, in, cm, mm, em, ex, px und % festgelegt werden. Nicht ganz so groß ist die Format-Auswahl bei Farbangaben: Hier steht Ihnen wie in HTML neben einer langen Liste vordefinierter Farbnamen wie blue, silver oder tomato die RGB-Schreibweise zur Verfügung. Diese kann nicht nur hexadezimal erfolgen – wo Rot zum Beispiel #ff0000 ist –, sondern auch dezimal und in Prozentwerten. Rot wäre also auch rgb(255, 0, 0) und rgb (100%, 0%, 0%). Das folgende Beispiel definiert einen 300 Pixel breiten Absatz mit dunkelroter Schrift auf beigefarbenem Hintergrund und 5 Millimeter Abstand zwischen Schrift und Element-Außenkante. Die Schriftart ist Verdana. Wenn Verdana nicht verfügbar ist, soll Helvetica gesucht werden; gibt es auch diese nicht, wird eine Standard-Schrift ohne Serifen gewählt.
Den Stil verbessern, das heisst den Gedanken verbessern.
(Netscape 4 zeigt den Hintergrund nicht korrekt an. Wie man diesen Fehler umgeht, erfahren Sie in Übung 12.) CSS-Positionierung und Block-Effekte Die für DHTML wichtigsten CSS-Eigenschaften habe ich bislang verschwiegen: Mit Style Sheets können Sie nämlich nicht nur bestimmen, wie ein Element aussehen (und klingen) soll, sondern auch, wo es im Browserfenster steht, wie viel von ihm zu sehen ist, und welche anderen Elemente es verdecken darf.
174
Grundlagen Um ein Element pixelgenau im Browserfenster zu positionieren, sind drei Angaben nötig. Zuerst muss mit position festgelegt werden, relativ zu welchem Punkt positioniert werden soll: entweder relativ zur normalen Position des Elements im Dokumentfluss oder relativ zur linken oberen Dokumentecke. Die entsprechenden Werte sind relative und absolute. (Steht das Element in einem anderen positionierten Element, dann ist eine »absolute« Angabe immer relativ zur Position des umgebenden Elements.) Mit Hilfe der Eigenschaften left und top wird dann der Abstand von diesem Punkt festgelegt. Diese Werte können auch negativ sein. Ein Beispiel: Das ist
nicht besonders gut zu lesen.
Durch die Angabe von position:absolute wird der div-Bereich aus dem normalen Dokumentfluss herausgenommen und mit einem Abstand von 10 Pixeln zur oberen und 20 Pixeln zur linken Seitenkante positioniert. Dadurch überlappt er den im Quelltext zuvor angegebenen Text und ist deshalb nicht besonders gut zu lesen. Sie können unangenehme Überraschungen vermeiden, wenn sie sich zur Positionierung auf div- und span-Elemente beschränken. Der Internet Explorer 4 kann beispielsweise alle anderen Elemente ohnehin nur relativ positionieren. Standardmäßig sind alle auf einer Seite definierten Elemente sichtbar. Mit der CSS-Eigenschaft visibility können aber auch unsichtbare Elemente erzeugt werden: Das ist überhaupt nicht gut zu lesen.
Wozu das gut sein soll, werden Sie demnächst erfahren. Es ist sogar möglich festzulegen, dass nur ein Stück eines Elements zu sehen ist. Dazu dient die Eigenschaft clip, der ein Rechteck mit vier Kanten-Werten zugewiesen wird: Das ist nur teilweilse gut zu lesen.
175
9 Drei Einführungen in DHTML Der erste Wert bestimmt den Abstand der Oberkante des sichtbaren Bereichs zur oberen Elementgrenze, der zweite die Entfernung zwischen der rechten Kante des sichtbaren Ausschnitts und der linken Elementgrenze, der dritte die zwischen der Unterkante des Ausschnitts und der oberen Elementgrenze, der vierte die zwischen linker Elementgrenze und linkem Rand des angezeigten Ausschnitts. Alle Angaben sind also relativ zur linken oberen Element-Ecke. Schließlich können Sie mit der Eigenschaft z-index noch die Reihenfolge festlegen, in der sich positionierte Elemente überlappen, das heißt: welches Element über welchem liegt. Der Wert von z-index ist einfach eine Zahl: Je höher die Zahl, desto höher liegt das Element über den anderen Elementen im Dokument. Tabelle 9.2 fasst die in diesem Abschnitt vorgestellten CSS-Eigenschaften zusammen und beendet damit unseren kleinen Ausflug in die Welt der Style Sheets. Tab. 9.2: Noch ein paar CSS-Eigenschaften
CSS- Eigenschaft
Beispiel
Erklärung
position
position: absolute
die Art der Positionierung (relativ oder absolut)
left
left: 100px
der horizontale Abstand zum normalen Ort des Elements bei relativer Positionierung, sonst der Abstand zum übergeordneten Element
top
top: -50px
der entsprechende vertikale Abstand
visibility
visibility: hidden
die Sichtbarkeit des Elements
clip
clip: rect(20px 50px 30px 0px)
der sichtbare Ausschnitt des Elements
z-index
z-index: 60
die »Höhe« des Elements in der Stapelreihenfolge
9.1.2
DHTML mit dem Netscape Navigator 4
Anders als die neueren Objektmodelle von Microsoft und W3C bietet Netscape 4 nur sehr eingeschränkten Zugriff auf die Elemente einer Webseite. Es gibt lediglich einen einzigen wirklich flexiblen Element-Typ: Den Layer. »Layer« bedeutet »Ebene«, und genau so sollten Sie sich Layer auch vorstellen: Eine Webseite kann aus verschiedenen Ebenen bestehen, von denen jede ein eigenes kleines Dokument enthält. Diese Dokument-Ebenen können nicht nur wie in Framesets nebeneinander, sondern nach Herzenslust übereinander und ineinander liegen und noch dazu jederzeit verschoben, versteckt, verdeckt und neu beschrieben werden.
176
Grundlagen Layer in HTML Um eine Seite bequem in mehrere Dokument-Ebenen einzuteilen, gibt es bei Netscape 4 den neuen HTML-Tag . Sehen wir uns ein Beispiel an:
Die Sprengung erfolgt mit Dynamit
Was zwischen und steht, also der Inhalt des Layer-Elements, bildet ein eigenständiges Dokument, welches an der im Start-Tag angegebenen Stelle pixelgenau positioniert wird. In unserem Fall besteht es nur aus dem einen, fett formatierten Wort »Dynamit«. Im Prinzip können Sie aber ein vollständiges Dokument von bis in den Layer schreiben. Es ist sogar möglich, externe Seiten einzubinden: Die Sprengung erfolgt mit
Sie sehen, Layer sind anders als gewöhnliche HTML-Elemente. So anders, dass kein Browser außer Netscape 4 sie versteht – nicht einmal die Nachfolgeversion Netscape 6. Deshalb sollten Sie in der Praxis besser auf verzichten. Das geht, weil Netscape einen Ersatz zur Verfügung stellt. Gewöhnliche HTMLElemente werden nämlich intern in Layer verwandelt, wenn man ihnen mit CSS-Angaben eine absolute oder relative Positionierung verleiht. Das folgende Dokument entspricht deshalb intern genau dem Beispiel vom Anfang dieses Abschnitts:
-Ersatz
Die Sprengung erfolgt mit Dynamit
Diese Schreibweise wird im Gegensatz zu auch von Internet Explorer und Netscape 6 verstanden. Wenn ich im Weiteren von Layern spreche, dann meine ich Objekte vom Typ Layer, egal ob sie mit dem -Tag oder mit , <span> oder
erzeugt wurden.
177
9 Drei Einführungen in DHTML Layer in JavaScript Das Objekt Layer
In JavaScript gibt es bei Netscape 4 den speziellen Objekt-Typ Layer, durch den Layer-Elemente repräsentiert werden. Layer-Objekte ähneln window-Objekten (vgl. Kapitel 7), insofern jeder Layer eine eigene document-Eigenschaft besitzt. Andererseits sind Layer selbst wiederum Eigenschaften eines documentObjekts, nämlich der Repräsentation des Dokuments, in dem sie definiert wurden. Schauen wir uns noch einmal ein Beispiel an: zwei Dokumente <style> #dynamo { position:absolute; left:20px; top:100px } mein Fahrrad: mein Dynamo:
In der Objekt-Hierarchie ist das gesamte Dokument als window.document vertreten. Es enthält einen Layer, in Form eines absolut positionierten div-Elements. Genau wie Bilder und Formulare werden die zu einem Dokument gehörenden Layer in einem Array (genauer: einer Collection) aufgelistet. Wie Sie in Kapitel 3 gelernt haben, erhalten Sie mit document.images[0] eine Referenz auf das erste Bild einer Seite. Entsprechend verweist document.layers[0]
auf den ersten Layer. Und weil Collection-Elemente im Gegensatz zu den Elementen eines gewöhnlichen Arrays auch über ihren Namen (bzw. ihre ID) herausgegriffen werden können, erreichen Sie den Layer mit der ID dynamo auch über diese Ausdrücke: document.layers["dynamo"] document.dynamo
Layer sind also, genau wie Image- oder Formular-Objekte, Eigenschaften von document.
178
Grundlagen Nun besitzt aber wie gesagt jeder Layer sein eigenes document-Objekt. Unsere Beispielseite besteht deshalb aus zwei, ineinander verschachtelten Dokumenten:
Die documentHierarchie
window.document
und window.document.dynamo.document
Das im Layer liegende Image-Objekt »dynamoBild« finden wir folglich unter window.document.dynamo.document.dynamoBild
Elemente, die in einem Layer stehen, gehören zum document-Objekt des Layers, nicht zum übergeordneten Gesamtdokument window.document. Sollten Sie versuchen, in unserem Beispiel mit document.images[1] oder document.dynamoBild auf das Dynamo-Bild zuzugreifen, erhalten Sie deshalb von Netscape 4 nur den Wert undefined. Layer-Eigenschaften Layer-Objekte besitzen eine Reihe von Eigenschaften, die Sie jederzeit lesen und ändern können. In Tabelle 9.3 sind die wichtigsten aufgelistet. Eigenschaft
Typ
Beschreibung
left
Number
die horizontale Position (in Pixeln) des Layers
top
Number
die vertikale Position des Layers
clip.top
Number
die Oberkante des sichtbaren Layer-Bereichs relativ zur Layer-Oberkante (in Pixeln). Ein Wert größer 0 schneidet also den Layer-Inhalt von oben ab.
clip.bottom
Number
die Unterkante des sichtbaren Layer-Bereichs relativ zur Layer-Oberkante
clip.left
Number
die linke Kante des sichtbaren Layer-Bereichs relativ zur linken Layer-Kante (in Pixeln). Ein Wert größer 0 schneidet also den Layer-Inhalt von links ab.
clip.right
Number
die rechte Kante des sichtbaren Layer-Bereichs relativ zur linken Layer-Kante.
zIndex
Number
die »Höhe« der Layer-Ebene in der Stapelreihenfolge. Layer mit hohem zIndex-Wert verdecken Layer mit niedrigerem Wert.
visibility
String
die Sichtbarkeit des Layers. show steht für sichtbar, hide für unsichtbar, bei inherit erbt der Layer seine Sichtbarkeit vom übergeordneten Layer. Anstatt show und hide können Sie auch visible und hidden verwenden.
src
String
URL-Angabe für externe Layer-Inhalte
Tab. 9.3: Einige Eigenschaften des Layer-Objekts
179
9 Drei Einführungen in DHTML Layer-Eigenschaften ändern
DHTML besteht beim Netscape Navigator im Wesentlichen darin, diese Werte so zu ändern, dass dadurch interessante Effekte entstehen. Zum Beispiel können wir eine Animation erzeugen, indem wir regelmäßig die left- und topKoordinaten eines Layers ändern: eine einfache Animation <style> #fahrrad { position:absolute; left:-100px; top:100px } <script> function bewegung(){ document.fahrrad.left+=5; setTimeout("bewegung()",50); } mein Fahrrad: CSS-Attribute entsprechen LayerEigenschaften
Dieses Beispiel finden Sie auf der CD als beispiel0901.html. Wie Sie vermutlich bemerkt haben, entsprechen viele der Layer-Eigenschaften bestimmten CSSAttributen. Tatsächlich werden diese Attribute von Netscape 4 nur in Verbindung mit Layern unterstützt. Das heißt, wenn Sie einen Bereich Ihrer Seite unsichtbar machen wollen, müssen Sie ihn in einen Layer stecken, zum Beispiel in ein positioniertes div-Element. Diesem Element verpassen Sie dann die Style-Angabe »visibility:hide« (oder »visibility:hidden«). Mit document.layerID.visibility = "show"
können Sie den Bereich später auf sichtbar schalten. Noch mehr dynamische Möglichkeiten ergeben sich aus der Tatsache, dass die document-Objekte von Layern alle Eigenschaften und Methoden von window.document besitzen. Sie können deshalb einen Layer mit document.layerID.document.write("einString"); document.layerID.document.close();
neu beschreiben. Damit können Sie den Inhalt eines Seitenbereichs nach dem Laden noch verändern, etwa in Reaktion auf eine Benutzer-Eingabe.
180
Grundlagen
9.1.3
DHTML mit dem Internet Explorer
Bei Microsoft entschloss man sich, dem DHTML-Modell von Netscape nicht zu folgen. Der Internet Explorer kennt weder den -Tag noch das LayerObjekt und bietet auch keinen vergleichbaren Ersatz. Stattdessen sind die CSSEigenschaften aller HTML-Elemente über JavaScript zugänglich und veränderbar. Das verschafft dem Internet Explorer Fähigkeiten, die weit über die von Netscape 4 hinaus gehen.
CSS-Eigenschaften aller HTML-Elemente sind über JavaScript zugänglich
Ein Beispiel:
ui, ein Rollover-Effekt
Dieser Absatz wechselt beim Überfahren mit der Maus Farbe und Schriftart. (Und dabei ist kein Link im Spiel!) Die Anweisung zur Änderung der Farbe ist: this.style.color = 'red';
Der Ausdruck this bezieht sich, wenn er in einem Event-Handler steht, auf das Element, für das der Handler definiert ist, also hier das p-Element. Wie Sie sehen, erreicht man die CSS-Eigenschaften eines Elements in JavaScript als Eigenschaften des jedem Element zugeordneten Objekts style. Wenn Sie genau hinsehen, wird Ihnen auffallen, dass die Style-Eigenschaften in JavaScript manchmal etwas anders geschrieben werden als in CSS. So wird aus fontfamily fontFamily. Ebenso fanden wir bei Netscape die Eigenschaft zIndex, die dem CSS-Attribut z-index entspricht. Die Regel ist einfach: Überall, wo ein MinusZeichen ist, wird dieses weggelassen und der nachfolgende Buchstabe groß geschrieben. Der Grund dafür ist, dass das Minus-Zeichen in JavaScript eben für Minus steht. Würden Sie also einElement.style.font-family schreiben, dann würde der JavaScript-Interpreter versuchen, den Wert des Ausdrucks family von dem des Ausdrucks einElement.style.font zu subtrahieren, wobei vermutlich nichts Sinnvolles herauskäme. Die document.all-Collection In der Praxis wollen wir oft die CSS-Eigenschaften eines Elements von außerhalb des Elements verändern, so dass wir nicht mit this darauf verweisen können. Wie dann? Die document.layers-Collection, in der bei Netscape 4 LayerElemente aufgelistet sind, existiert natürlich beim Internet Explorer nicht. Aber
document.all
181
9 Drei Einführungen in DHTML es gibt eine andere Collection, die uns weiterhilft. Erinnern Sie sich: Im IE sind nicht nur einige besondere HTML-Elemente dynamisch veränderbar, sondern wirklich alle. Die entsprechende Collection heißt folgerichtig document.all. Über document.all[27] erhalten Sie zum Beispiel das 28. HTML-Element auf der Seite (das erste, mit dem Index 0, ist das html-Element, das zweite in der Regel das head-Element und so weiter). Die Referenzierung über Index-Nummern ist allerdings eher unhandlich: Immer wenn Sie ein neues Element in Ihre Seite einfügen, müssen alle Nummern angepasst werden. Geben Sie deshalb besser den Elementen, die Sie ansprechen wollen, ein ID-Attribut, dann können Sie mit document.all["elementID"]
darauf zugreifen. Wenn Sie vermuten, dass das wie bei Image- und (bei Netscape) Layer-Objekten auch mit document.elementID geht, dann haben Sie sich getäuscht. Die HTML-Elemente sind nicht als direkte Eigenschaften von document verfügbar. Es geht aber trotzdem kürzer, nämlich über window.elementID oder einfach elementID. Hier noch einmal ein Rollover-Effekt, diesmal ohne »this«:
ich bin sehr dynamisch
Man kann, wie dieses Beispiel zeigt, auch Style-Eigenschaften ändern, die zuvor gar nicht explizit definiert wurden. Beim Lesen ist das allerdings anders: Nur diejenigen style-Eigenschaften lassen sich auslesen, die inline im HTMLTag angegeben oder dem Element nachträglich mit JavaScript zugewiesen wurden. Das sollten Sie vor allem beachten, wenn Sie Style Sheets in eigenen <style>Abschnitten definieren. Netscape 4 reflektiert die dort definierten Werte in den Layer-Eigenschaften, beim Internet Explorer aber werden Sie sie in den styleEigenschaften der betroffenen Elemente nicht finden. (Seit Version 5 des Explorers gibt es als Ersatz die Eigenschaft currentStyle, die stets den aktuellen Wert enthält. Das funktioniert allerdings nur in der Windows-Version.) Element-Inhalte ändern Es gibt noch andere Wege, Teile einer Seite mit Internet Explorer dynamisch zu verändern. Wenn Sie zum Beispiel eine Überschrift definiert haben:
182
Grundlagen Wau
dann können Sie diese nicht nur wann immer Sie wollen verstecken, vergrößern, verkleinern und verfärben, Sie können sogar ihren Inhalt austauschen. Dazu gibt es die Eigenschaften innerText und innerHTML, die sowohl gelesen als auch geschrieben werden dürfen: document.all["wau"].innerText = "Miau"
verwandelt die Überschrift »Wau« in »Miau«. Die Änderung ist sofort im Dokument sichtbar. Der Unterschied zwischen innerText und innerHTML kommt zum Tragen, wenn sich innerhalb eines Elements HTML-Tags befinden. Beim body-Element ist das zum Beispiel meistens so. Wenn Sie document.body.innerText auslesen, erhalten Sie den gesamten Inhalt des body, aber nicht die darin vorkommenden HTML-Tags. document.body.innerHTML dagegen liefert Ihnen den Inhalt mitsamt den Tags. Das folgende Beispiel erzeugt ein Dokument, dessen Inhalt Buchstabe für Buchstabe aufgebaut wird, ein bisschen so als würde man es gerade mit der Schreibmaschine schreiben. <script> var text = "Immerhin besitzt doch ein Mensch die Freiheit, " + "seine Hand auf den Kopf zu legen. (G.W. Leibniz, " + "Neue Abhandlung über den menschlichen Verstand, " + "Leipzig: Meiner 1915, S. 108)"; var zaehler = 0; function schreib(){ document.body.innerHTML+= text.charAt(zaehler); zaehler++; if (zaehler var zustand="sichtbar"; function einaus(){ if (zustand == "sichtbar"){ document.vielText.visibility = "hidden"; zustand = "unsichtbar"; } else {
190
Übungen document.vielText.visibility = "visible"; zustand = "sichtbar"; } } Hier steht ganz viel Text Text ein-/ausblenden
Schreiben Sie die Seite so um, dass darin kein Layer-Tag mehr vorkommt. mittel
9. Übung
Hier ein Ausschnitt aus einem anderen Dokument:
An welcher Position, relativ zum gesamten Dokument, liegt die linke obere Ecke des Layers BraunglasContainer? mittel
10. Übung
Mit welcher JavaScript-Anweisung kann das Bild »bier.jpg« in Übung 9 durch »milch.jpg« ersetzt werden? mittel
11. Übung
Als Nächstes ein Aquarium. Erstellen Sie mit einem Grafikprogramm ein paar Bilder von Blättern und Algen und positionieren Sie sie mit Style Sheets so im Fenster, dass ein hübsches Aquarium entsteht. Wählen Sie auch eine passende Hintergrundfarbe für den body. Es fehlen noch die Fische. Auch hier brauchen Sie geeignete Bilder, und zwar sollten Sie von jedem Fisch zwei haben: eins auf dem der Fisch nach rechts und
191
9 Drei Einführungen in DHTML eins auf dem er nach links schwimmt. Besonders elegant wird es, wenn Sie animierte Grafiken verwenden, dann können Sie sogar die Flossen schlagen lassen. Lassen Sie nun Ihre Fische langsam hin und her durchs Wasser ziehen. schwer
12. Übung
Layer sind durchsichtig: Überall dort, wo das Dokument eines Layers keine (oder durchsichtige) Elemente enthält, ist das unter dem Layer liegende Dokument zu sehen. Um den Layer undurchsichtig zu machen, müssen Sie ihm eine Hintergrundfarbe oder ein Hintergrundbild geben. Die CSS-Angaben dafür sind: background-image:url(dasHintergrundbild.jpg) layer-background-image:url(dasHintergrundbild.jpg) background-color:#6666ff; layer-background-color:#6666ff;
Sie sollten zusätzlich zum W3C-konformen background-color-Attribut stets auch layer-background-color angeben, denn mit background-color alleine werden nur die Layer-Bereiche farbig hinterlegt, auf denen Text oder HTML-Elemente stehen. Dasselbe gilt für background-image und layer-background-image. (Der Effekt ist nicht ganz einfach zu beschreiben – wenn Sie es ausprobieren, werden Sie sofort verstehen, was gemeint ist.) Schreiben Sie nun eine Seite mit fünf farbigen, leicht versetzt übereinander gestapelten Quadraten. Mit Hilfe von Formular-Buttons soll man die Stapelreihenfolge der Quadrate verändern können: Ein Button stellt die dunklen nach oben, einer die hellen, ein dritter sortiert sie nach dem Zufallsprinzip. Um einen Layer auf quadratische Form zu strecken, benutzen Sie das CSSAttribut clip: ein Quadrat mit Größ e 200x200 Pixel
Eine Zufallszahl zwischen 0 und 1 liefert die Anweisung Math.random(). mittel
13. Übung
Schicke Effekte lassen sich mit halbtransparenten Hintergrundbildern erzielen. Gemeint sind kleine Bilder im gif-Format, die gleichmäßig verteilte transparente Anteile haben (vgl. Abbildung 9.2).
192
Übungen Abbildung 9.2: Ein »halbtransparentes« Pixelmuster (vergrößert)
Für die nächste Aufgabe geben Sie einer Seite im body-Tag ein Hintergrundbild. Dann erstellen Sie einen Layer mit halbtransparentem Hintergrund, der einige Links enthält (der Layer natürlich, nicht der Hintergrund). Dieses Navigationsmenü soll anfangs aber nicht zu sehen sein. Erst wenn man auf einen Button klickt, erscheint es – und zwar indem es sich von oben nach unten ausfährt. Dazu ändern Sie einfach nach und nach den clip.bottom -Wert des Layers. Benutzen Sie folgende Style-Definition: <style type="text/css"> #menu { position:absolute; left:10; top:30; clip:rect(0px 200px 0px 0px); width:200px; background-image:url(semitrans.gif); padding:10px } a { font-weight:bold; color:black; text-decoration:none }
Das Bild »semitrans.gif« müssen Sie natürlich erst einmal erstellen (oder von der beiliegenden CD kopieren). Der nicht-transparente Anteil sollte dabei weiß sein. mittel
14. Übung
Eine der Stärken des Objektmodells von Netscape liegt in der einfachen Einbeziehung externer Inhalte in ein Dokument: Im Grunde kann jeder Layer auf einer Seite von einem anderen URL stammen. Wäre dieses Konzept weiterentwickelt worden, würden wir Frames kaum noch benötigen. Erweitern Sie die Lösung von Übung 13 so, dass bei der Auswahl eines Menüpunkts die dazugehörige Datei auf derselben Seite angezeigt wird. Auch hier soll der Hintergrund halb durchsichtig sein.
193
9 Drei Einführungen in DHTML
9.2.3
Übungen zum DHTML-Modell von Microsoft
leicht
15. Übung
Wie kann man im Internet Explorer mit JavaScript auf dieses Element verweisen?
Das Meer ist voll Wasser – das soll einer verstehen.
Und wie jetzt?
Das Meer ist voll Wasser – das soll einer verstehen.
leicht
16. Übung
Sehen Sie sich dieses Stück Quelltext an:
Das Meer ist voll Wasser – das soll einer verstehen.
<script> document.all["Satie"].visibility = "hidden";
Was passiert hier? Wie könnte man erreichen, was eigentlich beabsichtigt war? leicht
17. Übung
Schreiben Sie eine Seite mit drei Absätzen. Die Schriftfarbe dieser Absätze soll grau sein. Bewegt man sich aber mit der Maus darüber, wechselt sie auf schwarz. mittel
18. Übung
Erstellen Sie eine Schrift, die ganz klein auf der Seite auftaucht und dann immer größer wird. mittel
19. Übung
Noch ein zoom-Effekt. Diesmal soll die Schrift nicht nur größer werden, sondern auch dunkler. Außerdem sollen mehrere Texte nacheinander eingezoomt werden, die dann untereinander stehen bleiben.
194
Übungen Zur Angabe der Schriftfarbe verwenden Sie am besten die Dezimal-Schreibweise: rotesElement.style.color = "rgb(255,0,0)"
Der color-Wert ist hier ein String, bestehend aus »rgb(», dem Farbwert für Rot (0-255), gefolgt von einem Komma, gefolgt vom Grünanteil, gefolgt von noch einem Komma, dem Blauwert und einer schließenden Klammer. mittel
20. Übung
Auf einer Webseite steht ein Formular mit einem textarea-Feld und einem Button. Darunter befindet sich ein leeres graues Rechteck. Wenn man etwas in das Formular eingibt und den Button drückt, wird der Inhalt der textarea in das graue Rechteck kopiert. Der Witz dabei ist: Gibt man HTML-Formatierungen ein, dann werden diese im grauen Rechteck nicht angezeigt, sondern ausgewertet. Schreibt man zum Beispiel Kohl
dann erscheint im Rechteck das Wort »Kohl« fett formatiert. Schreiben Sie diese Anwendung. mittel
21. Übung
Sollten Sie bereits versucht haben, auch im Internet Explorer mit halbtransparenten Hintergrundbildern zu arbeiten, dann wird Ihnen nicht entgangen sein, dass IE zum Rendern hierfür wesentlich länger braucht als Netscape – zu lange selbst für einfache Animationen. Das ist aber nicht weiter schlimm, denn Internet Explorer kennt – zumindest in der Windows-Version – etwas viel Schöneres: CSS-Filter. Um ein positioniertes Element halb durchsichtig zu machen, brauchen Sie ihm lediglich die CSSAngabe filter:alpha(opacity=50) zu geben:
CSS-Filter
abwesend
Der Wert 50 liegt genau in der Mitte zwischen unsichtbar (0) und undurchsichtig (100). Wie alle style-Angaben lässt sich auch die »Filterstärke« mit JavaScript verändern. Schreiben Sie eine Seite, deren gesamter Inhalt nach dem Laden langsam eingefadet wird. schwer
22. Übung
In Übung 14 sahen Sie, wie man bei Netscape 4 mit Hilfe von Layern eine externe HTML-Seite nachträglich in ein bestehendes Dokument einfügen kann. Der Internet Explorer kennt kein Layer-Objekt. Wie könnte man den gewünschten Effekt trotzdem erreichen?
195
9 Drei Einführungen in DHTML
9.2.4
Übungen zum W3C DOM
leicht
23. Übung
Zeichnen Sie den DOM-Baum des folgenden Dokuments. Wo Wo kämen wir hin...
...wenn jeder sagte: Wo kämen wir hin?,
und keiner ginge, um zu sehen, wohin wir kämen, wenn wir gingen?
Vergessen Sie nicht, dass auch TextNodes im DOM eigene Objekte sind! leicht
24. Übung
Im Dokument der vorherigen Übung: Worauf verweisen diese beiden Ausdrücke? document.body.childNodes[0].firstChild document.childNodes[0].lastChild.firstChild. childNodes[0] leicht
25. Übung
Und worauf zeigt dieses Ungetüm? document.body.previousSibling.lastChild.parentNode. parentNode.childNodes[1].childNodes[1].previousSibling. lastChild
Gehen Sie Schritt für Schritt der Referenzierung durch den DOM-Baum nach. leicht
26. Übung
Wieso löschen diese Anweisungen nicht den Inhalt der Überschrift? var el = document.body.childNodes[0].firstChild; document.body.removeChild(el); mittel
27. Übung
Schreiben Sie eine Funktion, die, wenn Sie onload aufgerufen wird, alle Überschriften der Seite automatisch durchnummeriert. Das heißt, wenn die erste Überschrift »Der Treibhauseffekt« heißt, dann soll daraus »1. Der Treibhauseffekt« werden, und so weiter.
196
Übungen Gehen Sie der Einfachheit halber davon aus, dass alle Überschriften direkte Kinder des body-Elements sind, so dass Sie den DOM-Baum nicht weiter zu durchsuchen brauchen. Sie müssen allerdings herausfinden, ob es sich bei einem bestimmten Node um eine Überschrift handelt. Dabei hilft Ihnen die Element-Eigenschaft tagName, die den Tag-Namen in Großbuchstaben enthält, also zum Beispiel "H1". schwer
28. Übung
Verbessern Sie die Funktion aus der letzten Übung, so dass sie nicht nur die Überschriften durchnummeriert, sondern zusätzlich am Anfang der Seite ein kleines Inhaltsverzeichnis einfügt, das mit lokalen Links auf die einzelnen Unterabschnitte verweist. Die Einträge im Inhaltsverzeichnis sollen dem Text der dazugehörigen Überschriften entsprechen. schwer
29. Übung
Schreiben Sie eine JavaScript-Funktion, die ein neues Fenster öffnet und darin den DOM-Baum des Ausgangsfensters darstellt. Die Ausgabe könnte zum Beispiel so aussehen: + HTML | +--+ HEAD | | | +--+ TITLE | | | +-- ein Titel | +--+ BODY | +-- vergessen Sie nie den | +--+ B | +-- Grundgedanken leicht
30. Übung
Benutzen Sie das Skript aus der letzten Übung, um sich anzusehen, wie die neuesten Browser den DOM-Baum verschiedener Seiten intern abbilden. Dazu können Sie das Bookmark von der CD benutzen oder den Quelltext, den Sie testen wollen, einfach zusammen mit dem Script auf eine Seite stellen. Sie werden einige Besonderheiten entdecken, die Sie bei der Arbeit mit DOMMethoden beachten sollten.
197
9 Drei Einführungen in DHTML
9.3
Tipps
Tipp zu 4: 왘 Sie brauchen zwei Elemente, die beide denselben Text enthalten und leicht
versetzt zueinander positioniert werden. Vergessen Sie nicht, mit z-index die Stapelreihenfolge festzulegen. Tipps zu 11: 왘 Geben Sie allen Elementen im Aquarium einen z-index-Wert, damit ein
Eindruck von Tiefe entsteht. 왘 Um die Fische zu bewegen, stellen Sie sie in Layer und verändern in regel-
mäßigem Abstand deren left-Eigenschaft. Wenn ein Fisch am AquariumRand angekommen ist, dann tauschen Sie das Bild durch den in die andere Richtung schwimmenden Fisch aus. Tipps zu 12: 왘 Definieren Sie drei Funktionen hellhoch(), dunkelhoch() und mischen(),
die von den Formularbuttons onclick aufgerufen werden. In diesen Funktionen ändern Sie die zIndex-Werte der Layer. 왘 Möglicherweise spielen Sie mit der Idee, die div-Elemente dynamisch per
document.write zu schreiben. In diesem Fall sollten Sie auf jeden Fall die style-Angaben aus den Tags heraus nehmen. Ein Fehler in Netscape 4 führt dazu, dass andernfalls in solchen Situationen der Browser oft Unsinn macht. (Mehr über Netscape-Bugs erfahren Sie im Anhang zum nächsten Kapitel.) Für die vorliegende Aufgabe können Sie die Layer ruhig einzeln in HTML definieren – es sind ja nur 5. Tipps zu 14: 왘 Sie werden merken: Die Einbindung externer Dateien ist nicht ganz ausge-
reift. Beispielsweise verliert ein Layer alle seine Formatierungen, wenn Sie ihn mit einem neuen Dokument überschreiben, auch sein Hintergrundbild. Als Workaround könnten Sie nach dem Überschreiben den Hintergrund jeweils neu festlegen. Dazu gibt es die Anweisung: document.LayerID.background.src = "dasBild.gif"
Eine Alternative ist, zwei getrennte Layer einzusetzen: einen für den externen Inhalt, und einen anderen, etwas größeren, der darunter liegt und sozusagen das (halb durchsichtige) Papier für den Inhalt darstellt. 왘 Ein zusätzliches Problem entsteht dadurch, dass Sie mit der Zuweisung zu
src praktisch keinen Einfluss darauf haben, welche Breite der Layer annimmt, wenn eine externe Seite hineingeladen wird. Abhilfe bietet hier
198
Tipps die Layer-Methode load, die Sie statt der src-Zuweisung benutzen können und die einen Parameter für die gewünschte Breite hat: document.LayerID.load("seite.html",500)
Tipp zu 19: 왘 Setzen Sie bei jedem zoom-Schritt den color-String aus seinen Bestandtei-
len neu zusammen. Dabei können Sie dieselbe Variable für den Rot-, den Grün- und den Blau-Anteil einsetzen, der Farbwechsel erfolgt dann in Graustufen. Tipp zu 20: 왘 Lassen Sie sich nicht abschrecken, es ist ganz einfach. Sie brauchen nur
eine einzige Zeile JavaScript-Code. Tipps zu 21: 왘 Die Filter-Angabe ist ein String. Um sie zu ändern, müssen Sie also die
gewünschte Stärke in den String einbauen und der style-Eigenschaft filter zuweisen. 왘 Also so:
Element.style.filter = "alpha(opacity="+variable+")"; 왘 Welches Element würde sich anbieten, wenn auf die gesamte Seite ein Fil-
ter gelegt werden soll? Tipps zu 22: 왘 Die Seite, die eingebunden werden soll, muss irgendwohin geladen wer-
den. Laden wir sie ins aktuelle Fenster, verschwindet aber das bestehende Dokument. Wo könnte man die Seite sonst noch hin laden? 왘 Richtig: in einen Frame. Das ist jedoch nicht, was wir wollten: Framesets
sind schnöde, und Effekte wie in Übung 14 lassen sich damit nicht erzielen. Einen gewissen Fortschritt erreichen Sie, wenn Sie statt eines Framesets einen Iframe einsetzen. IE unterstützt schon seit Version 4 den HTML-Tag <iframe>, durch den Frames in ein normales Dokument eingebaut werden können. Wir definieren also einen Iframe und laden dort hinein die externe Seite. Das ist zwar besser, aber immer noch nicht das Wahre: Iframes sind eigene window-Objekte und als solche (jedenfalls bis IE5.5) immer undurchsichtig und durch andere Seitenelemente nicht verdeckbar. Weiter kommen Sie, indem Sie den Iframe zum Beispiel durch eine Größenangabe von 0 x 0 px unsichtbar machen. Wozu könnte das gut sein? 왘 Denken Sie an innerHTML.
199
9 Drei Einführungen in DHTML 왘 Sie können mit innerHTML den Inhalt des Iframe (oder vom darin liegenden
body) auslesen. Anschließend können Sie diesen Inhalt ebenfalls mit innerHTML in ein Element Ihrer Wahl auf der Hauptseite schreiben. 왘 Nun müssen Sie nur noch irgendwie mitbekommen, wann der Iframe fertig
geladen ist. Ein onload-Handler im iframe-Element funktioniert leider nicht. Was tun? 왘 Die einfachste Lösung ist: Sie schreiben in den body-Tag der externen Seite
einen onload-Handler, der eine Funktion auf der Hauptseite aufruft. Tipps zu 27: 왘 Wir unterscheiden nicht zwischen Überschriften erster, zweiter oder dritter
Ordnung, sondern zählen einfach alle durch. Überlegen Sie sich, wie Sie alle Kinder des body mit Hilfe von tagName darauf prüfen können, ob es sich um eine Überschrift handelt. 왘 Gehen Sie in einer for-Schleife die ChildNodes des body durch und prüfen
Sie bei jedem Durchgang, ob der aktuelle Node als TagName "H1", "H2", "H3", "H4" oder "H5" hat. Wenn ja, lesen Sie den Inhalt des Elements (sein erstes Kind) aus und überschreiben ihn durch denselben Inhalt mit der korrekten Zahl davor. Tipps zu 28: 왘 Halten Sie beim Umbenennen der Überschriften deren Text in einem Array
fest. Dann können Sie später aus dem Array das Inhaltsverzeichnis zusammenstellen. 왘 Es ist nicht anzunehmen, dass vor jedem Absatz bereits ein Anker-Element
steht, welches Sie nur mit einem lokalen Link anzuspringen brauchen. Denken Sie sich mindestens zwei Möglichkeiten aus, wie man trotzdem vom Inhaltsverzeichnis aus zu den Absätzen springen könnte. 왘 Die nahe liegende Möglichkeit ist, die fehlenden Anker-Elemente einfach
nachträglich zu erzeugen. Wenn Sie das versuchen, werden Sie zweierlei bemerken: 1. Es ist gefährlich, die Zahl der Einträge eines Node-Arrays zu erhöhen, während man ihn in einer for-Schleife durchläuft. Dadurch können Loops entstehen, die im schlimmsten Fall den Browser – beim Internet Explorer auch gerne das ganze Betriebssystem – zum Absturz bringen. 2. Aus unerfindlichen Gründen erkennt der Internet Explorer dynamisch erstellte Anker nicht. Weder tauchen Sie in der document.anchors Collection auf, noch können sie mit einem lokalen Link angesprungen werden. Gut also, dass Sie sich mindestens zwei Möglichkeiten ausgedacht haben.
200
Tipps Eine andere Möglichkeit wäre zum Beispiel, sich die Position der Überschriften im Dokument zu merken. Die Links im Inhaltsverzeichnis würden dann eine JavaScript-Funktion aufrufen, durch die die Seite an die entsprechende Position gescrollt wird. Es geht aber viel einfacher: Gemäß der HTML 4-Spezifikation des W3C können lokale Links nicht nur auf Anker-Elemente verweisen, sondern auf ein beliebiges Seitenelement, und zwar über dessen ID. Ein lokaler Link auf eine Überschrift kann deshalb in den neuesten Browsern auch so aussehen: zum ersten Absatz ... erster Absatz
Tipps zu 29: 왘 Sie müssen herausbekommen, ob es sich bei einem Node um ein Element
oder einen TextNode handelt. Dabei hilft Ihnen die Eigenschaft nodeType: Für Elemente ist ihr Wert 1, für TextNodes 3. (Die 2 steht für AttributNodes.) Den Tag-Namen eines Elements erhalten Sie über die Eigenschaft tagName. 왘 Wählen Sie geeignete HTML-Elemente, um die richtige Formatierung zu
erreichen. Es wäre beispielsweise gut, wenn Sie für die Einrückungen mehrere Leerzeichen hintereinander schreiben können, ohne verwenden zu müssen. Entities wie und ä sind im DOM eigene Nodes. Eine Anweisung wie text = document.createTextNode("Mäh")
ist deshalb nicht erlaubt. Sie dürfen aber den Umlaut direkt in den Quelltext schreiben:
Sonderzeichen im DOM
text = document.createTextNode("Mäh")
Das ist korrekt und sollte, sofern der richtige Zeichensatz angegeben wurde, auch wie gewünscht aussehen. Den Zeichensatz legen Sie beispielsweise mit diesem meta-Tag fest: <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
Alternativ können in JavaScript Sonderzeichen auch als Unicode-Sequenz maskiert werden. Dabei wird aus beispielsweise \u00A0. Erfahrungsgemäß funktioniert das zuverlässiger als die unmaskierte Schreibweise der Sonderzeichen. 왘 Sie könnten den ganzen Baum in ein pre-Element schreiben. Dadurch
bekommen Sie neben der Möglichkeit, beliebig viele Leerzeichen zu verwenden, auch gleich die gewünschte nicht-proportionale Schriftart. Als Ele-
201
9 Drei Einführungen in DHTML ment-Typ für die einzelnen Einträge empfielt sich div, weil das automatisch einen Zeilenumbruch mit sich bringt, ohne wie p gleich einen zu großen Absatz zu erzeugen. 왘 Schreiben Sie zuerst die Nodes der untersten DOM-Ebene. Gehen Sie dazu
die document.childNodes durch und erzeugen Sie für jedes darin liegende Element ein entsprechendes Element im Popup-Fenster. 왘 Wenn wir davon ausgehen, dass auf der untersten Ebene in einem korrek-
ten HTML-Dokument nie TextNodes stehen, sieht die Routine für diese unterste Ebene ungefähr so aus: var baum = popup.document.createElement("PRE"); for (var i=0; i<document.childNodes.length; i++){ var kind = document.childNodes[i]; var el = popup.document.createElement("DIV"); var divInhalt = "+ "+kind.tagName; var txtNode = popup.document.createTextNode(divInhalt); el.appendChild(txtNode); baum.appendChild(el); holeKinder(document.childNodes[i]); } 왘 Was jetzt noch fehlt, ist die Funktion holeKinder(), die rekursiv die Unter-
elemente im Dokument-Baum durchsucht und im Popup-Fenster auflistet. Sie ähnelt der abgedruckten Routine für die unterste Ebene – der Hauptunterschied ist, dass sie auch TextNodes auflistet. 왘 Überlegen Sie sich, wie Sie die aus Plus- und Minuszeichen bestehende
Baum-"Grafik« hinbekommen könnten. Das ist vermutlich der schwierigste Teil der Übung. Wenn er Ihnen nicht so recht gelingen will, dann zerbrechen Sie sich darüber nicht den Kopf, sondern lassen ihn einfach weg – oder sehen in der Lösung nach, wie es geht.
9.4
Lösungen
Lösung zu 1: 1. CSS-Eigenschaften können direkt im HTML-Tag des betroffenen Elements mit Hilfe des style-Attributs definiert werden. 2. CSS-Angaben können in einem eigenen style-Element im head der Seite stehen. 3. Um Formate für mehrere Seiten zu nutzen, können diese auch als externe Dateien eingebunden werden. Dazu gibt es den link-Tag.
202
Lösungen Lösung zu 2: Wir legen in einem style-Element die Schriftgröße aller Absätze auf 15pt fest: p { font-size: 15pt }
Den einen Absatz, der anders dargestellt werden soll, versehen wir mit einer ID und weisen ihm so eine andere Schriftgröße zu: #wichtigerAbsatz { font-size: 25pt }
Lösung zu 3: body { width:520px; padding:20px; font-family:Arial }
So jedenfalls sollte es funktionieren. Wenn Sie es ausprobieren, werden Sie aber bemerken, dass sich hier bei Netscape und Internet Explorer Bugs eingeschlichen haben: Der Internet Explorer ignoriert die width-Angabe, bei Netscape 4 geht häufig die Schriftart verloren, zum Beispiel in und nach einer Tabelle. Auf solche Bugs werden Sie noch häufig stoßen. Es empfielt sich deshalb, CSSAngaben immer sofort mit den Browsern zu testen, um sich später eine mühsame Fehlersuche zu ersparen. Lösung zu 4: Die Style-Definition für die beiden Elemente könnte so aussehen: <style type="text/css"> #schrift { position:absolute; left:25px; top: 15px; color:#3333ff; font-size:30px; z-index:20; } #schatten { position:absolute; left:27px; top: 18px; color:#bbbbbb; font-size:30px; z-index:10; }
Im body brauchen nur noch die beiden Elemente angelegt zu werden: Wo Schatten ist, ist Licht. Wo Schatten ist, ist Licht.
Lösung zu 5: Der gesamte Inhalt des body steht in einem Layer. Die linke Kante des Layers ist auf -100 Pixel gesetzt, das heißt auf 100 Pixel links vom linken Rand des Browserfensters. Da außerdem die Layerbreite auf 100 Pixel beschränkt ist, liegt der ganze Layer außerhalb des sichtbaren Bereichs. Lösung zu 6: Die horizontale Position des Layers ist in der left-Eigenschaft des LayerObjekts festgelegt. Um die Layerposition zu verändern, brauchen Sie diesen Wert nur zu überschreiben:
203
9 Drei Einführungen in DHTML document.layers["nichts"].left = 0;
Jetzt befindet sich der Layer genau in der linken oberen Ecke des Fensters. Lösung zu 7: Die neun Ausdrücke, die Sie gefunden haben sollten, sind: document.layers[0].document.images[0] document.layers[0].document.images["etwas"] document.layers[0].document.etwas document.layers["nichts"].document.images[0] document.layers["nichts"].document.images["etwas"] document.layers["nichts"].document.etwas document.nichts.document.images[0] document.nichts.document.images["etwas"] document.nichts.document.etwas
Es gibt sogar noch mehr. Zum Beispiel können Sie auf Collection-Elemente auch zugreifen, indem Sie das Element als Eigenschaft der Collection ansprechen: document.layers.nichts.document.images.etwas
Was dagegen nicht funktioniert, ist: document.images[0] document.images["etwas"] document.etwas
Lösung zu 8: Die Faustregel lautet: Alle durch mit absoluter CSS-Positionierung ersetzen. Da positionierte div-Elemente von JavaScript als Layer behandelt werden, ändert sich am Skript-Bereich überhaupt nichts. Nur der Layer selbst wird anders geschrieben: Hier steht ganz viel Text
Lösung zu 9: Die Positionsangaben im Style-Attribut der div-Tags werden (genau wie die in einem layer-Tag) immer relativ zu demjenigen Dokument ausgewertet, in dem das div-Element sich befindet. Die Beispielseite enthält vier Dokumente: Das Dokument des BraunglasContainers liegt innerhalb des AltglasContainerDokuments, dieses wiederum liegt innerhalb des RecyclingHofs, welcher sich schließlich im Hauptdokument befindet. Um die Position des BraunglasContainers zu bestimmen, müssen Sie die Koordinaten zusammenzählen: Seine
204
Lösungen linke Kante ist 0px entfernt von der linken Kante des BraunglasContainers, diese Kante ist 100px rechts von der des RecyclingHofs, welche 300px von der linken Fensterkante entfernt liegt. Die resultierende x-Koordinate ist also 400px. Die y-Koordinate ergibt, wie Sie leicht nachprüfen können, 310px. Lösung zu 10: Zum Beispiel mit document.RecyclingHof.document.AltglasContainer.document. BraunglasContainer.document.Bierflasche.src = "milch.jpg";
Wenn Ihnen Übung 7 Spaß gemacht hat, können Sie jetzt die 255 anderen Möglichkeiten aufzählen, wie man eine Referenz auf das Bild erhalten kann. Lösung zu 11: Zuerst verteilen wir ein paar Pflanzen:
z-index:30">
z-index:10">
z-index:40">
z-index:20">
Wie Sie sehen, war ich faul und habe nur zwei Grafiken erstellt, die ich in verschiedene Größen skaliere. Genauso mache ich es bei den Fischen:
Jetzt müssen wir die Fische animieren. Dazu brauchen wir JavaScript.
205
9 Drei Einführungen in DHTML Es gibt zahlreiche Herangehensweisen für Animations-Effekte. Hier ein ganz einfacher Vorschlag. Im Laufe dieses und des nächsten Kapitels werden Ihnen noch elegantere Lösungen begegnen. Erst einmal halten wir die aktuelle Bewegungsrichtung unserer Fische fest: var richtung_fisch1 = "rechts"; var richtung_fisch2 = "links";
Dann brauchen wir eine Funktion, die die Fische um einen Schritt vorwärts bewegt und gegebenenfalls die Richtung wechselt: function bewegeFische(){ if (richtung_fisch1=="rechts") { // Fisch schwimmt nach rechts if (document.fisch1.left100) document.fisch1.left-=3; else { // wenden richtung_fisch1 = "rechts"; document.fisch1.document.f1.src="fischre.gif"; } } // Dasselbe fuer den zweiten Fisch: if (richtung_fisch2=="rechts") { if (document.fisch2.left0) document.fisch2.left-=2; else { richtung_fisch2 = "rechts"; document.fisch2.document.f2.src="fischre.gif"; } } }
206
Lösungen Mit setInterval rufen wir diese Funktion alle 50 Millisekunden auf: setInterval("bewegeFische()",50);
Für flüssige Bewegungen sollten Sie stets einen Zeitabstand von 30-50 Millisekunden wählen. Ein gewisses Ruckeln ist dabei leider immer noch zu erkennen. Niedrigere Werte können die Browser aber (zumindest auf Windows-Betriebssystemen) nicht verarbeiten. Nur auf MacIntosh und Unix/Linux sind wirklich weiche Bewegungsabläufe möglich. Lösung zu 12: Wir schreiben erst einmal die Layer. So sieht der erste aus: Quadrat 1
Um die Stapelreihenfolge festzulegen, habe ich ihm neben einer Position und einer Hintergrundfarbe einen festen z-index-Wert zugewiesen. Lässt man bei einer Layer-Definition die z-index-Angabe weg, dann gibt Netscape dem Layer automatisch den z-index-Wert des zuletzt erzeugten Layers. Bei gleichem zindex-Wert liegt der zuletzt definierte Layer oben. Die anderen Quadrate sehen ähnlich aus, ich ändere einfach Namen, Position, Farbe und z-index: Quadrat 2 Quadrat 3 Quadrat 4
207
9 Drei Einführungen in DHTML Quadrat 5
Als Nächstes brauche ich ein Formular zur Steuerung der Stapelreihenfolge:
Jetzt fehlen nur noch die JavaScript-Funktionen. Diese sind ganz einfach: function hellhoch() { for (i=1;i-1)
erkennt die MacIntosh-Ausgabe des Internet Explorers, if (navigator.userAgent.toLowerCase().indexOf("aol")>-1)
testet auf AOL-Browser, if (navigator.userAgent.toLowerCase().indexOf("icab")>-1)
auf iCab und if (navigator.userAgent.toLowerCase().indexOf("opera")>-1)
auf Opera. Strategie 1: Verschiedene Seiten für verschiedene Browser Nachdem wir unsere Besucher nun in hübsche Gruppen einteilen können, müssen wir uns überlegen, was wir dort mit ihnen anfangen. Zum Beispiel könnten wir mehrere Versionen der Seite erstellen und auf der Startseite die Browser entsprechend umleiten. Das sieht dann ungefähr so aus:
BrowserUmleitung
if (document.getElementById) // W3C-kompatibel location.replace("w3cdhtml.html"); else if (document.layers) // Netscape 4 location.replace("netscape4dhtml.html"); else if (document.all) // IE4 location.replace("ie4dhtml.html"); else location.href = "nodhtml.html";
Hier werden Netscape 4, Internet Explorer 4 und W3C-kompatible Browser (sowie solche, die vorgeben W3C-kompatibel zu sein) auf eine jeweils eigene Seitenversion geschickt. Alle anderen Browser kommen, sofern sie JavaScript
225
10 DHTML: Cross-Browser-Strategien verstehen, nach nodhtml.html. Die Puristen, die kein JavaScript verstehen, bleiben einfach auf der Umleitungsseite stehen, wo wir Ihnen beispielsweise einen Link nach nodhtml.html anbieten können. Verwenden Sie für JavaScript-Umleitungen möglichst immer location.replace() und nicht location.href. Denn nur bei location.replace kommt man mit dem Back-Button des Browsers von der DHTML-Seite aus wie gewünscht zurück auf die Seite, auf der man vorher war (vgl. Kapitel 3). Browser-Umleitungen haben den Vorteil, dass Sie auf den DHTML-Seiten keine Rücksicht mehr auf Inkompatibilitäten zu nehmen brauchen. Es gibt einfach für jedes Objektmodell eine eigene Seite. Der Nachteil ist, dass Sie auf diese Weise schnell sehr viele Seiten erhalten, die sich nur schwer warten lassen. Außerdem müssen Sie zum Beispiel dafür sorgen, dass, wenn jemand einen Link auf Sie setzt, dieser immer auf die Umleitungsseite führt. Strategie 2: Verschiedene Anweisungen für verschiedene Browser Häufig ist es bequemer, nur eine einzige Seite für alle Browser zu haben. In diesem Fall ist dafür zu sorgen, dass jede Browserversion nur diejenigen SkriptBereiche zu sehen bekommt, die sie versteht. Die Puristen bereiten hier wenig Schwierigkeiten: Sie ignorieren ohnehin alle Skripte. Schwieriger wird es mit den Browsern der zweiten Gruppe, die JavaScript kennen und deshalb versuchen werden, die DHTML-Funktionen auszuführen – um dabei unweigerlich zu scheitern. Dieses Problem ließe sich im Grunde einfach lösen, indem man in allen scriptTags das Attribut language auf JavaScript1.2 setzt. Ihr Inhalt wird dann von Netscape und Internet Explorer erst ab Version 4 ausgewertet. Leider geben jedoch auch die neueren Opera-Versionen vor, JavaScript1.2 zu unterstützen, und Netscape 3 ignoriert Sprachangaben bei extern eingebundenen Skripten völlig. SkriptVerzweigung
Am besten verzweigt man deshalb in den Skript-Blöcken selbst je nach Browser auf die entsprechenden Anweisungen: <script language="JavaScript1.2">
226
Grundlagen ... Anweisungen für alle anderen ... } // -->
Von diesen Verzweigungen braucht man in der Praxis eine ganze Menge, denn bei DHTML kommt fast in jedem Skriptblock, in jeder Funktion zum Beispiel, etwas vor, was nicht alle Browser verstehen. Es ist deshalb üblich, am Anfang der Seite Abkürzungen anzulegen: var n4 = document.layers; var ie = document.all; var w3c = document.getElementById;
Dann sieht von da an eine Abfrage auf Netscape 4 zum Beispiel nur noch so aus: if (n4) { ... }
Am besten fangen Sie an der Stelle auch die Browser ab, die das jeweilige Objektmodell nicht ausreichend verstehen. Wenn Sie wie ich finden, Opera habe in der w3c-Kategorie nichts verloren, dann sieht die Abfrage aufs W3C DOM so aus: var w3c = (document.getElementById && (navigator.userAgent.toLowerCase().indexOf("opera")==-1)
Strategie 3: Cross-Browser-Funktionen Nehmen wir an, Sie möchten die visibility-Eigenschaft eines positionierten div-Elements ändern. Cross-Browser geht das so: if (document.getElementById) // W3C document.getElementById("divID").style.visibility = "hidden"; else if (document.layers) // Netscape 4 document.layers["divID"].visibility = "hidden"; else if (document.all) // IE 4/5 document.all["divID"].style.visibility = "hidden";
So etwas ist in Ordnung, wenn es ein- oder zweimal auf einer Seite verwendet wird. Spätestens beim zwanzigsten Wechsel von visibility werden Sie diese Vorgehensweise aber als ziemlich unhandlich empfinden. Besser wäre es, wenn man eine Funktion hätte, die die ganze Verzweigung übernimmt, und die man jedes Mal nur noch aufzurufen braucht. Wir definieren also eine Cross-Browser-Funktion verstecke(), der die ID eines positionierten Elements übergeben wird, woraufhin sie dessen visibilityEigenschaft auf "hidden" schaltet:
227
10 DHTML: Cross-Browser-Strategien function verstecke(id) { if (document.getElementById) { // W3C document.getElementById(id).style.visibility = "hidden"; } else if (document.layers) { // Netscape 4 document.layers[id].visibility = "hidden"; } else if (document.all) { // IE 4/5 document.all[id].style.visibility = "hidden"; } }
Der Vorteil dürfte einleuchten: Ist die Funktion einmal definiert, brauchen Sie nur noch verstecke("elementID") aufzurufen, um das Element mit der ID elementID unsichtbar zu machen. Die inkompatiblen Objektmodelle der einzelnen Browser brauchen Sie dabei nicht mehr zu interessieren. Cross-BrowserLibraries
Wenn Sie DHTML auf mehr als nur einer Seite einsetzen, dann können Sie sich eine Reihe allgemein verwendbarer Cross-Browser-Funktionen basteln, diese in einem externen Skript-File ablegen und auf jeder DHTML-Seite einbinden. Der Cross-Browser-Code wird durch diese Funktionen wesentlich einfacher und übersichtlicher – Browser-Verzweigungen sind auf der Seite selbst fast gar nicht mehr nötig. Wir werden im Übungsteil einige Cross-Browser-Funktionen schreiben. Fertige und umfassendere Bibliotheken finden Sie im Internet, zum Beispiel auf http://www.brainjar.com/dhtml/dhtmllib.html. Strategie 4: Cross-Browser-Objekte JavaScript ist zwar keine streng objektorientierte Sprache wie Java, aber trotzdem lässt sich damit recht gut objektorientiert programmieren. Folglich kann man statt Cross-Browser-Funktionen auch eigens erstellte Cross-BrowserObjekte verwenden. Angenommen, Sie haben einen DHTML-Newsticker auf Ihrer Seite. Dann könnten Sie einen Objekttyp Ticker definieren und für Ihren Newsticker eine Instanz dieses Typs erzeugen: newsticker = new Ticker();
Zur Steuerung des Tickers verwenden Sie Methoden, die Sie für das Objekt definiert haben, um Beispiel: newsticker.stop();
oder newsticker.richtungswechsel();
228
Grundlagen Keine Angst, wenn Ihnen das im Moment nicht allzu viel sagt. In den Übungen zu diesem Teil wird genau erklärt, wie die einzelnen Schritte funktionieren und wozu das Ganze gut ist. Auch fertige Bibliotheken mit nützlichen Cross-Browser-Objekten gibt es im Internet, etwa unter http://www.dansteinman.com/dynduo/. (Mehr und aktuellere Links finden Sie auf der Webseite zu diesem Buch: http://www.javascriptworkshop.de/.) Noch ein paar Grundregeln Die wichtigste Regel für DHTML ist wahrscheinlich: Testen. Die vorhandenen Browser enthalten in Sachen DHTML derart viele Bugs, dass Sie darauf vertrauen können, bei jeder Seite mindestens einem von ihnen zu begegnen. Wenn Sie regelmäßig Ihre Skripte testen, dann können Sie ziemlich genau herausfinden, ab wann etwas nicht mehr funktioniert und die entsprechende Stelle verändern. Andernfalls werden Sie Stunden damit verbringen, in einem 1.000-Zeilen-Code diese eine Stelle zu suchen, an der dem Browser etwas nicht gefällt.
Testen
Testen Sie mit möglichst vielen Browsern, wenn es geht auch auf verschiedenen Plattformen. Sie können ohne Weiteres mehrere Netscape-Versionen nebeneinander installieren – laden Sie sich also gleich ein paar davon herunter. Vor allem einen aus der Reihe 4.01-4.05 sollten Sie besitzen, da diese noch kein JavaScript1.3 verstehen. Leider ist es zumindest unter Windows praktisch unmöglich, mehrere Versionen des Internet Explorers gleichzeitig zu betreiben. Bevor Sie sich an die Programmierung machen, überlegen Sie sich erst einmal, wie Sie das gewünschte Ergebnis auf allen Ziel-Browsern realisieren können. Oft ist es hilfreich, dabei zuerst an Netscape 4 zu denken, denn dessen DHTML-Fähigkeiten sind in vieler Hinsicht die schwächsten. Ein Ansatz für Netscape 4 lässt sich in der Regel ohne allzu große Probleme auf Internet Explorer übertragen – anders herum geht das häufig nicht.
Nachdenken
Wollen Sie beispielsweise eine Animation erzeugen, dann könnten Sie dazu mit Internet Explorer die style-Eigenschaften eines beliebiges Elements verändern. Für Netscape 4 brauchen Sie aber einen Layer, weshalb Sie am besten von Anfang an ein positioniertes div-Element verwenden. Wenn Sie einen Ansatz für Netscape 4 und Internet Explorer gefunden haben, dann dürfte Ihnen das W3C DOM kein Kopfzerbrechen mehr bereiten: In der Regel können Sie genauso vorgehen wie beim Internet Explorer – lediglich die syntaktischen Unterschiede müssen berücksichtigt werden. Zum Beispiel heißt es beim W3C nicht document.all["elementID"], sondern document. getElementById("elementID"). Die schon bei Netscape 3 vorhandenen Collections document.images, document.forms usw. existieren übrigens im W3C DOM nach wie vor.
229
10 DHTML: Cross-Browser-Strategien
10.2
Übungen
DHTML-Übungen mit mehr Interaktion zwischen Webseite und Besucher finden Sie im Kapitel »Erweitertes Event-Handling«. leicht
1. Übung
Hier ist ein Stück aus einem HTML-Dokument: Nichts ist besser als nichts.
Wie spricht man das Element mit der ID sohn in Netscape 4 an, wie im Internet Explorer, und wie im W3C DOM? leicht
2. Übung
Wie könnte man für alle drei Objektmodelle den sohn aus der vorigen Übung über seine clip-Eigenschaft(en) nachträglich unsichtbar machen? leicht
3. Übung
Welche Anweisungen verschieben für alle DHTML-Browser das Element vater aus Übung 3 an die Koordinaten 10/200? Nachdem dieses Skript ausgeführt wurde, wo steht dann das Element sohn? leicht
4. Übung
Nehmen wir an, Sie wollen beim Klick auf einen Link den Inhalt einer Tabellenzelle wechseln. Wie lässt sich das mit Internet Explorer und Netscape 6 erreichen? mittel
5. Übung
Gibt es zur vorigen Übung auch eine Lösung für Netscape 4? leicht
6. Übung
Schreiben Sie eine Seite, auf der stets die aktuelle Zeit im Format »Stunden:Minuten:Sekunden« angezeigt wird. Tauschen Sie dazu den Inhalt eines positionierten Elements einmal pro Sekunde durch die neue Zeit aus.
230
Übungen leicht
7. Übung
Programmieren Sie eine Funktion beschreibe(), die cross-browser den Inhalt eines positionierten Elements überschreibt. Der neue Inhalt wird als String an die Funktion übergeben, ebenso die ID des positionierten Elements. leicht
8. Übung
Wir wollen noch ein paar weitere Cross-Browser-Funktionen zusammenstellen. Schreiben Sie als Nächstes eine Funktion bewege(), die das Element mit der übergebenen ID an die ebenfalls als Parameter übergebenen Koordinaten stellt. mittel
9. Übung
Die nächste Funktion heißt schiebe(). Sie stellt ebenfalls das gewünschte Element an die gewünschte Position, allerdings nicht ruckartig, sondern in einer gleitenden Bewegung. Die Zahl der pro Schritt zurückzulegenden Pixel, also die Geschwindigkeit, wird als zusätzlicher Parameter übergeben. Wenn Sie Angst vor Zahlen haben, dann machen Sie eine Version, die das Element nur in einer Richtung verschiebt, also entweder horizontal oder vertikal. mittel
10. Übung
Die Objektmodelle von Netscape 4, Internet Explorer und W3C unterscheiden sich besonders stark beim Schreiben und Lesen der clip-Werte positionierter Elemente. Netscape 4 stellt dafür eine Reihe getrennter Eigenschaften bereit: clip.top; clip.right; clip.bottom; clip.left; clip.height; clip.width;
Beim Internet Explorer und ebenso beim W3C gibt es hingegen nur eine einzige style-Eigenschaft clip, in deren Wert, einem String, die vier clip-Kanten versteckt sind. Zum Beispiel: "rect(0px 200px 100px 10px)"
In der Praxis erweist sich das Modell von Netscape 4 meist als bequemer: String-Manipulationen sind mühsam, und die Reihenfolge der Kantenwerte bei IE und W3C (oben, rechts, unten, links) kann sich auch keiner merken. Schreiben Sie deshalb vier Cross-Browser-Funktionen zum Clippen: clipTop(), clipRight(), clipBottom() und clipLeft(). Wenn als Parameter nur die Element-ID übergeben wird, sollen diese Funktionen den aktuellen Wert der
231
10 DHTML: Cross-Browser-Strategien Kante, für die sie zuständig sind, zurückgeben. Wird hingegen zusätzlich ein weiterer Parameter übergeben, ändern sie die Kante auf den übergebenen Wert. leicht
11. Übung
Jetzt wollen wir unsere kleine Funktionsbibliothek einmal praktisch anwenden. Schreiben Sie eine Seite, deren Inhalt wie ein Filmabspann von unten nach oben über den Bildschirm läuft. Benutzen Sie dazu die Funktionen schiebe() und bewege(). Letztere brauchen Sie, um den Inhalt am Anfang an die Unterseite des Fensters zu bringen. Wie Sie die Fensterhöhe herausbekommen, erfahren Sie in Anhang 2 zu diesem Kapitel. mittel
12. Übung
Verbessern Sie die Lösung der vorigen Übung, so dass die Laufschrift erst mit 100px Abstand vom unteren Fensterrand erscheint und bereits 100px vor dem oberen Fensterrand wieder verschwindet. Das ergibt nicht nur einen hübschen Rand, es verhindert auch, dass man den Inhalt bereits vorzeitig lesen kann, indem man nach unten scrollt. Verwenden Sie weiterhin nur ein einziges positioniertes Element. mittel
13. Übung
Wir verbessern noch einmal unser Lauftext-Skript: Der Inhalt soll nun nicht mehr automatisch durchlaufen. Stattdessen stellen Sie zwei Links auf die Seite, mit denen man den Text nach oben bzw. nach unten bewegen kann. leicht
14. Übung
In den nächsten Übungen sehen wir uns an, wie man selbst definierte Objekttypen für den Cross-Browser-Einsatz anlegt. Auf den ersten Blick mag Ihnen dieser Weg, besonders wenn Sie mit objektorientierter Programmierung noch nicht so vertraut sind, vielleicht abwegig erscheinen. Für komplexe Seiten ist er aber oft die sauberste und übersichtlichste Methode. Wir werden einen Objekttyp XElement definieren, der für alle DHTML-Browser ungefähr das darstellt, was Netscape 4 durch den eingebauten Objekttyp Layer anbietet. Objekttypen definiert man durch eine Konstruktorfunktion: function XElement() { }
Wir können nun beispielsweise mit var grauschwarzeEbene = new XElement()
ein neues Objekt (eine Instanz) vom Typ XElement erzeugen.
232
Übungen Eine leere Konstruktorfunktion macht in der Regel wenig Sinn. In der Konstruktorfunktion werden wir vielmehr diejenigen Eigenschaften definieren, die alle Instanzen des Objekttyps besitzen sollen. Das sieht dann zum Beispiel so aus: function XElement() { this.x = 0; this.y = 0; }
Die Eigenschaften x und y sollen für die Koordinaten des XElements (das heißt: seine linke obere Ecke) stehen. Wie in Kapitel 4 erklärt verweist this auf die jeweils durch den Aufruf des Konstruktors erzeugte Instanz. Jede Instanz des Objekttyps XElement soll für ein bestimmtes div-Element auf der HTML-Seite stehen. Diese Elemente liegen vermutlich nicht alle in der linken oberen Ecke des Browserfensters. Wir könnten dem Konstruktor die aktuelle Position in Form von zwei Funktionsargumenten übergeben: function XElement(x, y) { this.x = x; this.y = y; } var grauschwarzeEbene = new XElement(100, 200);
Wir werden aber anders vorgehen: Der Konstruktor bekommt nur eine einzige Argumentstelle, und zwar übergeben wir ihm die ID des div-Elements. Es ist dann Aufgabe der Konstruktorfunktion, herauszufinden, an welcher Position sich das Element mit dieser ID befindet. Schreiben Sie nun diesen Konstruktor. mittel
15. Übung
Angenommen, auf einer HTML-Seite befindet sich ein positioniertes div-Element: ein Gedanke
Mit Hilfe des Konstruktors XElement() wird ein Objekt angelegt, das dieses Element repräsentiert: var grauschwarzeEbene = new XElement("grauschwarz");
Was passiert, wenn Sie anschließend folgende JavaScript-Anweisungen aufrufen?
233
10 DHTML: Cross-Browser-Strategien alert(grauschwarzEbene.y); grauschwarzeEbene.y = 50; alert(grauschwarzeEbene.y); leicht
16. Übung
Erweitern Sie die Konstruktorfunktion XElement(), so dass sie neben der Position des Elements auch dessen Breite und Höhe sowie seinen z-index-Wert als Eigenschaften widerspiegelt. Außerdem sollen XElement-Objekte eine Eigenschaft el besitzen, die auf das HTML-Element verweist, das sie repräsentieren. Wie Sie die Breite und Höhe eines Elements herausbekommen, steht in Anhang 2. mittel
17. Übung
Es empfielt sich, neben el – der Referenz auf das repräsentierte Element selbst – noch eine Eigenschaft bereitzustellen, die auf die style-Property des Elements verweist. Nennen wir diese Eigenschaft css. Netscape 4 kennt kein style-Objekt, deshalb zeigt css hier einfach auf das Element selbst. Der Konstruktor sieht jetzt so aus: function XElement(id) { if (document.getElementById) { this.el = document.getElementById(id); this.css = this.el.style; } else if (document.all) { this.el = document.all[id]; this.css = this.el.style; } else if (document.layers) { this.el = document.layers[id]; this.css = this.el; } this.x = parseInt(this.css.left); this.y = parseInt(this.css.top); this.zIndex = this.css.zIndex; if (document.layers) { this.breite = this.el.document.width; this.hoehe = this.el.document.height; } else { this.breite = this.el.offsetWidth; this.hoehe = this.el.offsetHeight; } }
234
Übungen Bisher ist unser Cross-Browser-Objekt noch ziemlich nutzlos. Das ändert sich, wenn wir ihm jetzt ein paar Methoden verpassen. Objekt-Methoden werden genau wie Objekt-Eigenschaften definiert. Der Unterschied ist nur, dass dem Methodennamen als Wert die Referenz auf eine Funktion zugewiesen wird (also der Funktionsname ohne Klammern, vgl. Kapitel 4). Mit Hilfe von Methoden lösen wir die Schwierigkeit, der wir in Übung 15 begegneten. Sie erinnern sich: Die Änderung von XElement-Eigenschaften wirken sich nicht auf das repräsentierte HTML-Element aus. Geben Sie dem Objekttyp XElement eine Methode gehNach(x,y), die das HTML-Element an die übergebenen Koordinaten stellt. Die x- und y-Eigenschaften des XElement-Objekts sollen natürlich dabei auch aktualisiert werden. leicht
18. Übung
Mittlerweile sollte Ihnen dämmern, wozu Cross-Browser-Objekttypen gut sein können: Immer wenn Sie ein positioniertes Element definiert haben, das Sie hin- und herbewegen wollen, brauchen Sie nur eine entsprechende Instanz von XElement zu erzeugen und können dann dessen gehNach()-Methode benutzen, ohne sich um die drei verschiedenen Objektmodelle Gedanken zu machen. Und sollte demnächst zur allgemeinen Freude noch ein viertes Objektmodell auftauchen, dann brauchen Sie nur den Konstruktor XElement() zu verändern. Die konkreten Anwendungen bleiben, solange sie nur die Objektmethoden einsetzen, unberührt. Im Gegensatz zu Cross-Browser-Funktionen sind außerdem zusammengehörige Variablen und Funktionen auch ordentlich zusammen abgelegt. Erweitern Sie XElement noch um zwei weitere Methoden: zeige() und verstecke(). mittel
19. Übung
Als erste Anwendung des Cross-Browser-Objekttyps XElement lassen wir es nun regnen. Schreiben Sie eine Webseite mit 50 kleinen, grauen, länglichen Layern: die Regentropfen. Jeder Regentropfen wird durch ein eigenes XElement repräsentiert. Regentropfen fallen, wie Sie wissen, gewöhnlich von oben nach unten. Wenn sie am unteren Fensterrand angekommen sind, stellen Sie sie wieder nach ganz oben. Die horizontale Position überlassen Sie am besten dem Zufall. schwer
20. Übung
Setzen Sie XElement() jetzt ein, um das nicht besonders originelle, aber vermutlich beliebteste DHTML-Skript zu erstellen: Ein Menü, das ähnlich wie die Dateimanager vieler Betriebssysteme beim Klick auf einen Hauptpunkt eine Reihe von Unterpunkten einblendet und bei erneutem Klick wieder ausblendet.
235
10 DHTML: Cross-Browser-Strategien Ihr Menü braucht nur eine Untermenütiefe zu unterstützen, das heißt, Sie können annehmen, dass keine Unterpunkte im Menü vorgesehen sind, die selbst wieder Unterunterpunkte einblenden. schwer
21. Übung
Entwickeln Sie einen neuen Objekttyp KlappMenu, der ein ganzes Menü von der Art, wie wir es in der letzten Übung gebaut haben, repräsentiert. Im Unterschied zu XElement() erzeugt der KlappMenu-Konstruktor selbst die Elemente, die er repräsentiert. Das bedeutet: Im Konstruktor wird mit document.write der HTML-Quelltext des Menüs geschrieben. Objekte vom Typ KlappMenu müssen deshalb erzeugt werden, noch bevor die Seite fertig gerendert ist. Dem Konstruktor werden neben der Position des Menüs zwei Arrays übergeben: eine Liste mit den Hauptmenü-Punkten und eine mit den Untermenüs. Die Elemente des zweiten Arrays sind also selbst wieder Arrays, sie enthalten die Namen der Untermenü-Punkte und den dazugehörenden URL. Ein Aufruf könnte also wie folgt aussehen: ... <script> klappHaupt = new Array("Früchte", "Tonarten"); klappSub = new Array( new Array("Apfel", "apfel.html", "Birne", "birne.html", "Kirsche", "kirsche.html"), new Array("a Moll", "amoll.html", "cis Dur", "cisdur.html") ); klapp = new KlappMenu(20, 100, klappHaupt, klappSub);
Diese Anweisungen sollen ausreichen, um das gewünschte Menü, voll funktionsfähig, an der gewünschten Position auf der Seite erscheinen zu lassen. mittel
22. Übung
Eines haben wir bislang sowohl bei den Cross-Browser-Funktionen als auch bei den Methoden von XElement übersehen: Layer können ineinander verschachtelt sein:
236
Übungen Hier liegt für Netscape 4 das eine positionierte div-Element im document des andern. Wenn wir die ID tochter an den XElement-Konstruktor oder eine der Cross-Browser-Funktionen übergeben, erhielten wir eine Fehlermeldung, denn document.layers["tochter"]
existiert nicht. Die korrekte Referenz wäre zum Beispiel: document.layers["mutter"].document.layers["tochter"].
Wie könnte man diese Schwierigkeit umgehen? leicht
23. Übung
Viele der in diesem Kapitel vorgestellten Lösungen führen auf DHTML-unkundigen Browsern zu Fehlermeldungen. Zum Abschluss deshalb noch zwei Aufgaben zum Umgang mit solchen Besuchern. Häufig legt man, besonders bei komplexen Auftritten, neben der DHTML-Seite eine Alternativseite für alle die Browser an, die kein DHTML beherrschen. Es handelt sich dabei meist um eine einfache HTML-Seite, die die wichtigsten Inhalte und Links des Angebots zusammenfasst. (Bei Angeboten ohne Inhalt enthält sie folgerichtig nur den Hinweis, dass man den falschen Browser verwendet.) Schreiben Sie eine Browser-Umleitung, die Netscape ab Version 4 und die Windows-Versionen von Internet Explorer 4 und 5 zur DHTML-Seite schickt, alle anderen dagegen auf die Alternativ-Seite. Überlegen Sie sich, ob man auch Browser, die kein JavaScript verstehen, direkt zur Alternativseite bringen könnte (und wenn ja, wie). mittel
24. Übung
Würden Sie für jede Browserversion eine eigene Seite schreiben, dann hätten Sie am Ende etwa 200 Versionen, die bei jeder Änderung alle überarbeitet werden müssen. Am schönsten wäre es deshalb, eine einzige Seite für alle zu haben. Die DHTML-Seite sollte also am besten auch mit Browsern betrachtet werden können, die kein DHTML oder sogar kein JavaScript verstehen. Ob das möglich ist, müssen Sie im Einzelfall selbst entscheiden. Bei der folgenden Seite ist es möglich. Allerdings muss dazu der Quelltext etwas umgebaut werden. Versuchen Sie es. <script> function menuEinAus(){ ... } function zeige(seite){ ...
237
10 DHTML: Cross-Browser-Strategien } eins
zwei
drei
test
Vielleicht fallen Ihnen diese Zeilen in der init()-Funktion auf: if (testDiv.captureEvents) { // Netscape 4 testDiv.captureEvents(Event.MOUSEDOWN); document.captureEvents(Event.MOUSEMOVE | Event.MOUSEUP); } testDiv.onmousedown = mdown; testDiv.onmouseup = mup; document.onmousemove = mmove;
305
11 Erweitertes Event-Handling Anders als in den vorherigen Übungen setze ich captureEvents() nur bei Netscape 4, nicht aber bei Netscape 6 ein. Warum? Weil captureEvents() ausschließlich eine Methode von window-, document- und layer-Objekten ist. testDiv wird von Netscape 4 als Layer angesehen. Netscape 6 kennt aber keine Layer, weshalb er beim Versuch, auf testDiv die Methode captureEvents() anzuwenden, einen Fehler bringen würde. Man kann auf captureEvents() für Netscape 6 ruhig verzichten, weil dieser wie Internet Explorer Event-Bubbling unterstützt. Wenn Sie Schwierigkeiten beim »einfachen« Drag&Drop hatten, dann kopieren Sie sich die Lösung und versuchen Sie jetzt, sie so weiter zu entwickeln, dass sie universell einsetzbar wird. (Auf der CD finden Sie das oben abgedruckte Skript im Verzeichnis »sonstiges« als »dragndrop.html«). Hier ist die fertige Lösung: <script language="JavaScript1.2" type="text/javascript">
306
Lösungen // die andern Handler nicht vergessen: if (document.captureEvents) // Netscape document.captureEvents(Event.MOUSEMOVE | Event.MOUSEUP); document.onmouseup = mup; document.onmousemove = mmove; } window.onload = init; function mdown(e){ // Wir müssen onmousedown herausfinden, welches div// Element aktiviert wurde. Dazu benutzen wir "this". // Dann zählen wir den bislang höchsten z-Index-Wert eins // nach oben und weisen ihn dem aktiven div zu. Der Rest // ist wie gehabt. aktivDiv = this; if (document.layers) aktivDiv.zIndex = aktivZindex++; else aktivDiv.style.zIndex = aktivZindex++; drag = "aktiv"; aktivX = (e) ? e.pageX : event.clientX + document.body.scrollLeft; aktivY = (e) ? e.pageY : event.clientY + document.body.scrollTop; return false; } function mmove(e){ // fast keine Änderungen im Vergleich zum einfachen // Drag&Drop if (drag == "inaktiv") return; var neuX = (e) ? e.pageX : event.clientX + document.body.scrollLeft; var neuY = (e) ? e.pageY : event.clientY + document.body.scrollTop; var distX = (neuX-aktivX); var distY = (neuY-aktivY); aktivX = neuX; aktivY = neuY; if (document.layers) { aktivDiv.left += distX; aktivDiv.top += distY; } else { aktivDiv.style.left = parseInt(aktivDiv.style.left) + distX; aktivDiv.style.top =
307
11 Erweitertes Event-Handling parseInt(aktivDiv.style.top) + distY; } } function mup(e){ drag = "inaktiv"; } //-->
Was noch fehlt, ist die Speicherung des Layouts in einem Cookie. Mit Hilfe der Cookie-Funktionen aus Kapitel 8 ist das gar nicht so schwierig. Zuerst ergänzen wir die Funktion mup() so, dass bei jedem Mouseup-Event die aktuellen div-Positionen in den Cookie geschrieben werden. Dazu gehen wir den Array divs[] durch, fragen die Koordinaten ab und hängen sie zusammen an einen String. Aus diesem String müssen wir später die Koordinaten wieder rekonstruieren können, deshalb stellen wir zwischen zwei zusammengehörige x/y-Werte jeweils ein Komma und trennen sie vom nächsten Wertpaar mit einem Plus-Zeichen: function mup(e){ drag = "inaktiv"; // Cookie-String zusammenstellen: var cookieString = ""; for (i=0; i"; // paint the applet repaint(); } public void paint(Graphics g) { // draw background g.setColor(Color.blue); // blue g.fillRect(0,0, getSize().width, getSize().height); // draw message g.setColor(Color.white); g.drawString(sMessage, 0, 20); } } Listing 12.1: HelloLiveConnect.java
Dieses Applet gibt eine Textnachricht in weißer Schrift auf blauem Hintergrund aus.
318
Grundlagen Wie können wir nun mit dem Applet in Verbindung treten, uns LiveConnecten? Prinzipiell ist alles recht einfach: Sie können auf öffentliche Variablen des Applets lesend und schreibend zugreifen sowie öffentliche Methoden aufrufen. Variablen und Methoden werden in Java durch das Schlüsselwort public als öffentlich zugänglich gekennzeichnet. In obigem Listing sind dies u.a. die String-Variable sMessage und die Methoden db(String sText) und get JavaVersion(). Daneben besitzt jedes Applet eine Reihe weiterer öffentlicher Standard-Methoden. Eine davon ist von besonderem Interesse: repaint(). Wenn sich die Inhalte von Variablen, die die grafische Ausgabe des Applets beeinflussen, geändert haben, sollten Sie immer ein repaint() durchführen, entweder innerhalb des Applets selbst oder per JavaScript. Erst sein Aufruf löst das Neuzeichnen aus.
public
Greifen wir nun einmal per JavaScript auf die Variable sMessage zu: alert(document.myApplet.sMessage);
Ebenso simpel ist der Aufruf der Methode db(String sText). Aktivieren Sie bitte vorher die Java-Konsole: document.myApplet.db("We are LiveConnected!");
Methoden können auch Rückgabewerte liefern. Rufen Sie die Methode get JavaVersion() auf, die einen String mit Informationen über die Version der JVM zurückliefert: alert(document.myApplet.getJavaVersion());
Das vollständige Beispiel finden Sie auf dem beiliegenden Datenträger unter Buchdaten\kapitel12\beispiele\03\index.html. Bitte beachten Sie, dass Sie ein Applet erst manipulieren sollten, nachdem es vollständig geladen wurde. Abhängig von der Größe und Anzahl der benötigten class-Dateien kann der Download aus dem Netz durchaus einige Zeit in Anspruch nehmen. Generell gilt die Regel: Applets erst nach dem onLoad-Event (siehe Event-Handling) des Bodys manipulieren. Um ganz sicherzugehen, fragen Sie die Existenz des Applets ab, bevor Sie auf seine Methoden oder Properties zugreifen: if (document.myApplet) { alert(document.myApplet.getJavaVersion()); }
Applet kommuniziert mit Browser Unabhängig von LiveConnect sieht die Sprache Java Methoden vor, mit denen ein Applet den Applikationskontext (in unserem Fall: den Web-Browser) zur Anzeige von (Status)-Informationen veranlassen kann. Diese Methoden kön-
319
12 LiveConnect nen durchaus nützlich sein, daher sollen sie hier auch Erwähnung finden. Im Einzelnen sind dies: showDocument(URL url); showDocument(URL url, String sTarget); showStatus(String sStatus);
Im Web-Browser lösen diese Methoden folgende Aktionen aus: showDocument(...) showStatus(...)
Die Methode showDocument(URL url) lädt eine unter dem URL url befindliche HTML-Seite in das aktuelle Browser-Fenster, showDocument(URL url, String sTarget) lädt diese in einen HTML-Frame beziehungsweise ein Window namens sTarget. Dabei sind die reservierten Namen _self, _parent, _top und _blank als Ziel zulässig. So ruft beispielsweise URL url = URL("http://www.addison-wesley.de/"); showDocument(url, "_blank");
die Homepage unseres Verlags in einem neuen Browser-Fenster auf. Mit showStatus(String sStatus) können Sie den Text sStatus in die Statuszeile des Web-Browsers schreiben. Diese Methode kann also mit der JavaScript-Anweisung window.status = sStatus;
verglichen werden. Ein exemplarischer Zugriff auf die Statuszeile des Browsers kann so aussehen: showStatus("Applet talks to browser.");
Neben der Ausführung obiger Aktionen kann ein Applet noch Informationen über seine Herkunft und die JVM einholen. Mit den Methoden URL getDocumentBase(); URL getCodeBase(); getDocumentBase() getCodeBase()
erhalten wir den URL der einbettenden HTML-Seite beziehungsweise des Verzeichnisses der Klassendatei des Applets. Dies kann von Bedeutung sein, wenn etwa Dateien aus dem gleichen Verzeichnis manuell nachgeladen werden müssen. Mit der Methode String System.getProperty(String sPropName);
getProperty(String)
können verschiedene Systemeigenschaften der JVM abgefragt werden. Für uns sind besonders die Eigenschaften "java.version", "java.vendor" und "browser" interessant. Letztere ist nicht Bestandteil des Java-Standards, sondern kommt nur in den JVMs von Web-Browsern vor. import java.awt.*;
320
Grundlagen public class Java2Browser extends java.applet.Applet { java.awt.TextArea tb_info = new java.awt.TextArea("",0,0,TextArea.SCROLLBARS_VERTICAL_ONLY); public void init() { setLayout(new BorderLayout(0,0)); setSize(426,266); add(BorderLayout.CENTER,tb_info); String sInfo = "getDocumentBase(): " + getDocumentBase().toString() + "\n" + "getCodeBase(): " + getCodeBase().toString() + "\n"; String props[] = {"java.vendor", "java.version", "browser"}; for (int i=0;i<props.length; i++) sInfo += props[i] + ": " + System.getProperty(props[i]) + "\n"; tb_info.setText(sInfo); } } Listing 12.2: Java2Browser.java
In Beispiel 4 können Sie sehen, welche Werte diese Eigenschaften in Ihrem Browser haben. Das vollständige Beispiel finden Sie auf dem beiliegenden Datenträger unter Buchdaten\kapitel12\beispiele\04\index.html. Applet steuert JavaScript Dieser Abschnitt setzt durchschnittliche Java-Kenntnisse voraus. Sie sollten insbesondere wissen, wie man den Java-Compiler konfiguriert. Wir werden sehen, wie man von einem Java-Applet aus auf JavaScript-Objekte zugreifen und deren Methoden aufrufen und Properties lesen bzw. setzen kann. Die Browser-JVMs beinhalten zur Ansprache von JavaScript-Objekten ein JavaPackage, das nicht Bestandteil des Standardumfangs von Java ist. Dabei handelt es sich um das Package netscape.javascript. Im Netscape Navigator gibt es dies seit der Version 3.0, im Internet Explorer seit der Version 4.0.
netscape.javascript
321
12 LiveConnect Bevor wir uns näher mit den Objekten und Methoden dieses Packages beschäftigen, müssen wir wissen, wie Applets kompiliert werden, die LiveConnect benutzen. Innerhalb des Applets wird zunächst das Package über die import-Anweisung bekannt gemacht: import netscape.javascript.*; ... Classpath
Nun müssen wir den Classpath des Java-Compilers so anpassen, dass er anschließend das Package netscape.javascript bearbeiten kann. Wie Sie den Classpath Ihres Compilers anpassen, hängt von der Entwicklungsumgebung ab, mit der Sie arbeiten. Konsultieren Sie bitte die entsprechende Dokumentation. Wenn Sie mit dem Internet Explorer arbeiten, sollten Sie in der Regel unter C:\Windows\JAVA\Packages
eine Reihe von ZIP-Dateien finden. Die Namen dieser Dateien sind auf jedem System unterschiedlich. Die Datei, die am größten ist (ca. 5,6 MB), etwa C:\Windows\JAVA\Packages\BLN5V7VR.ZIP
ist das Archiv, welches das Java-Package netscape.javascript enthält. Diesen Namen fügen Sie dem bestehenden Classpath hinzu. Wenn Sie mit dem Netscape Navigator auf Windows-Systemen arbeiten, heißt das entsprechende Archiv in der Regel C:\Programme\Netscape\Communicator\Program\Java\classes\java40.jar
und unter UNIX-Systemen zum Beispiel /opt/netscape/java/classes/java40.jar
Sollten Sie Ihren Web-Browser in ein anderes Verzeichnis installiert haben, muss der Dateiname entsprechend anders gewählt werden. Bitte beachten Sie, dass sich im Navigator Version 4.6 ein Fehler in die JVM eingeschlichen hat. Das Package netscape.javascript kann nicht benutzt werden. Ein Zugriff darauf liefert immer eine Fehlermeldung. Mit der Version 4.61 wurde dieser Fehler von Netscape wieder behoben. Die Version 4.6 findet sich nicht im Download-Archiv von Netscape. Auf einigen Mirrors ist sie jedoch noch verfügbar, falls Sie damit experimentieren möchten. Kleiner Tipp am Rande: Öffnen Sie das Archiv einmal mit WinZIP (oder einem vergleichbarem Tool) und schauen Sie sich die Einträge im Unterverzeichnis netscape/javascript an. Dort finden Sie die Java-Klassen, die wir verwenden werden.
322
Grundlagen Bevor wir uns an die Programmierung eines LiveConnect-Applets begeben, müssen wir zunächst noch die einbettende HTML-Seite entsprechend vorbereiten. Dem Applet ist explizit der Zugriff auf die JavaScript-Objekthierarchie zu erlauben. Dies geschieht durch die Angabe des Attributes mayscript:
mayscript
Das Package netscape.javascript stellt uns im Wesentlichen eine Klasse JSObject zur Verfügung, durch deren Instanzen JavaScript-Objekte repräsentiert werden. Die Klasse JSObject hat unter anderem folgende Methoden:
JSObject
public public public public public public public public
static JSObject getWindow(java.applet.Applet applet); Object getMember(String sMemberName); Object getSlot(int iSlotIndex); void setMember(String sMemberName, Object oValue); void setSlot(int iSlotIndex, Object oValue); void removeMember(String sMemberName); Object call(String sMethodName, Object[] args); Object eval(String sCode);
Mit Hilfe dieser Methoden können JavaScript-Objekte des hierarchischen Browser-DOMs leicht auf Java-Objekte innerhalb des Applets abgebildet werden. Die Wurzel der gesamten Objekthierarchie bildet das window-Objekt, dem alle anderen Objekte untergeordnet sind:
window
try { JSObject window = JSObject.getWindow(this); } catch (Exception e) { }
wobei this eine Referenz auf das Applet ist. Mit der Methode call() eines solchen Objekts können Sie seine Methoden aufrufen, zum Beisiel:
call(...)
try { JSObject window = JSObject.getWindow(this); Object[] args = {"Hello LiveConnect!"}; window.call("alert", args); } catch (Exception e) { }
323
12 LiveConnect eval(...)
Mit der JavaScript-Methode eval(String sCode) wird der JavaScript-Code in sCode ausgeführt. So gibt try { JSObject window = JSObject.getWindow(this); window.eval("s=''; for(i=0; i"; nRepeats = 1; // paint the applet repaint(); } public void paint(Graphics g) { // draw background g.setColor(Color.blue); g.fillRect(0,0, getSize().width, getSize().height); // draw message g.setColor(Color.white); for (int i=1; i 0 – X Position: >=0 1 – Y Position: >=0 2 – X Scale: Percentage 3 – Y Scale: Percentage 6 – Alpha: 0-100 10 – Rotation: 0-360
Im Event-Handler für den Button-Click gehen Sie so vor: var s = document.myForm.mySelect; var iPropID = parseInt(s.options[s.selectedIndex].value); var dValue = parseFloat(document.myForm.myValue.value); document.myPlug.TSetProperty("/", iPropID, dValue);
Beachten Sie, dass die Property-ID als Ganzzahl und der entsprechende Wert als Fließkommazahl übergeben werden. Die vollständige Lösung finden Sie auf dem beiliegenden Datenträger unter \kapitel12\uebungen\12\index.html. Lösung zu 13 Zunächst betten Sie das Applet ein:
Dann müssen Sie dem Applet mitteilen, welchen Wertebereich der Scrollbalken haben soll: var min = parseInt(document.myForm.myMin.value); var max = parseInt(document.myForm.myMax.value); document.myApplet.setHook("changeProperty", min, max); // save prop ID in global var var s = document.myForm.mySelect; iPropID = parseInt(s.options[s.selectedIndex].value);
Wir speichern die Property-ID in einer globalen Variable iPropID, um diese später sofort zur Verfügung zu haben:
360
Lösungen function changeProperty(sValue) { document.myForm.myCurrent.value = sValue; document.myPlug.TSetProperty("/", iPropID, parseFloat(sValue)); }
Der Übersicht halber geben wir den aktuellen Wert des Scrollbalkens in einem input-Feld namens myCurrent aus. Die vollständige Lösung finden Sie auf dem beiliegenden Datenträger unter \kapitel12\uebungen\13\index.html. Lösung zu 14 var l = navigator.plugins.length; var s ="Plug-ins: #" + l + ""; for (var i=0; i