Heutzutage verwenden wir JavaScript ziemlich anders als früher. Mit dem Aufkommen von Ajax und dem Hype um Web-Applikationen und Social Networks werden unsere JavaScripte immer größer und komplizierter. Da ist es nicht leicht, den Überblick zu behalten. Das wird vor allem problematisch wenn man die Applikation in einem einzigen, großen JavaScript erstellt. Persönlich bin ich sehr dafür, Applikationen in kleine, logische Bausteine aufzuteilen und diese dann mit der richtigen Logik bei Bedarf zusammenzusetzen. Das erlaubt das parallele Arbeiten mehrerer Entwickler am gleichen Projekt und führt ausserdem zu kleineren Applikationen, da Module per Bedarf nachgeladen werden. Hier erkläre ich die ersten Schritte, wie man eine solche Applikation erstellen kann.
Am allerwichtigsten ist es, den JavaScript Code durch „Namespacing“ vor dem Überschreiben durch andere Skripte zu bewahren. Das geht am einfachsten indem man den gesammten Code in einem globalen Objekt speichert, zum Beispiel meineTolleApplikation:
var meineTolleApplikation = {};
Alle Komponenten der Applikation werden Attribute dieses Objektes und man kann auch gleich von Anfang an testen ob DOM Scripting unterstützt wird oder Sprachdateien laden und so weiter.
Der nächste Schritt ist dem Hauptobjekt ein Komponentenobjekt hinzuzufügen. Darin definiert man die einzelnen Komponenten mit Namen, URL und einem Boolean der angibt ob die Komponente geladen ist oder nicht.
var meineTolleApplikation = {
komponenten :{
formular:{
url:'formular.js',
geladen:false
},
navigation:{
url:'navigation.js',
geladen:false
},
attachment:{
url:'attachment.js',
geladen:false
},
lightbox:{
url:'lightbox.js',
geladen:false
}
}
};
Technisch gesehen kann man das "geladen" Attribut auch weglassen, aber so ist es sauberer. Als nächsten Schritt ist es sinnvoll eine neueKomponente Methode anzufügen, die es erlaubt, dynamisch Komponenten zur Applikation hinzuzufügen. Das wird am einfachsten dadurch erreicht, indem man neue SCRIPT Elemente dem Dokumentenkopf hinzufügt.
var meineTolleApplikation = {
komponenten :{
formular:{
url:'formular.js',
geladen:false
},
navigation:{
url:'navigation.js',
geladen:false
},
attachment:{
url:'galerie.js',
geladen:false
},
lightbox:{
url:'lightbox.js',
geladen:false
}
},
neueKomponente:function(komponente){
var c = this.komponenten[komponente];
if(c && c.geladen === false){
var s = document.createElement('script');
s.setAttribute('type', 'text/javascript');
s.setAttribute('src',c.url);
document.getElementsByTagName('head')[0].appendChild(s);
}
}
};
Dadurch kann man neue Komponenten der Applikation hinzufügen wenn diese noch nicht geladen sind:
if(!meineTolleApplikation.komponenten.galerie.geladen){
meineTolleApplikation.neueKomponente('galerie');
};
Das ist allerdings nicht wirklich sicher, da wir nicht wissen ob die Skripte wirklich geladen wurden. Um das einwandfrei sicherzustellen ist das einfachste, jeder Komponente eine Zeile hinzuzufügen, die dem Hauptobjekt meldet, das sie geladen ist:
var meineTolleApplikation = {
komponenten :{
formular:{
url:'formular.js',
geladen:false
},
navigation:{
url:'navigation.js',
geladen:false
},
galerie:{
url:'galerie.js',
geladen:false
},
lightbox:{
url:'lightbox.js',
geladen:false
}
},
neueKomponente:function(komponente){
var c = this.komponenten[komponente];
if(c && c.geladen === false){
var s = document.createElement('script');
s.setAttribute('type', 'text/javascript');
s.setAttribute('src',c.url);
document.getElementsByTagName('head')[0].appendChild(s);
}
},
komponenteVerfuegbar:function(komponente){
this.komponenten[komponente].geladen = true;
}
}
Zum Beispiel sollte das galerie.js Skript sich als letzte Zeile beim Hauptobjekt anmelden:
meineTolleApplikation.galerie = function(){
// [... other code ...]
}();
meineTolleApplikation.komponenteVerfuegbar('galerie');
Der letze Schritt ist eigentlich nur eine nette Maßnahme gegenüber den Entwicklern der Applikation. Wir fügen den Aufruf einer "lauscher" Methode hinzu, die den Namen einer jeden Komponente erhält, die geladen wurde:
var meineTolleApplikation = {
komponenten :{
formular:{
url:'formular.js',
geladen:false
},
navigation:{
url:'navigation.js',
geladen:false
},
galerie:{
url:'galerie.js',
geladen:false
},
lightbox:{
url:'lightbox.js',
geladen:false
}
},
neueKomponente:function(komponente){
var c = this.komponenten[komponente];
if(c && c.geladen === false){
var s = document.createElement('script');
s.setAttribute('type', 'text/javascript');
s.setAttribute('src',c.url);
document.getElementsByTagName('head')[0].appendChild(s);
}
},
komponenteVerfuegbar:function(komponente){
this.komponenten[komponente].geladen = true;
if(this.lauscher){
this.lauscher(komponente);
};
}
};
Das erlaubt es Entwicklern die lauscher Methode zu schreiben, die darauf wartet das Komponenten geladen werden und dann auf diese reagiert:
meineTolleApplikation.lauscher = function(komponente){
if(komponente === 'galerie'){
zeigegalerie();
}
};
Da das Hauptobjekt veränderbar ist können Entwickler das komponenten Objekt mit eigenen Komponenten erweitern und innerhalb einer lauscher Funktion andere abhängige Komponenten nachladen. Sagen wir zum Beispiel wir haben eine neuekomponente mit Daten und Beschreibungen in eigenen Dateien:
meineTolleApplikation.lauscher = function(komponente){
if(komponente === 'neuekomponente'){
meineTolleApplikation.neueKomponente('beschreibungen');
};
if(komponente === 'beschreibungen'){
meineTolleApplikation.neueKomponente('daten');
};
if(komponente === 'daten'){
meineTolleApplikation.neuekomponente.init();
};
};
meineTolleApplikation.komponenten.neuekomponente = {
url:'neue.js',
geladen:false
};
meineTolleApplikation.komponenten.beschreibungen = {
url:'beschreibungen.js',
geladen:false
};
meineTolleApplikation.komponenten.daten = {
url:'daten.js',
geladen:false
};
meineTolleApplikation.neueKomponente('neuekomponente');
Wenn man seine Applikation auf diese Art und Weise plant hat man totale Kontrolle über den Status der einzelnen Komponenten und kann sogar CSS bei Bedarf nachladen.
Falls Sie diese Idee mögen und sich fragen ob das auch schon mal in einer echten Applikation angewandt wurde möchte ich auf die Yahoo! User Interface Library weisen, im Speziellen die YAHOO_config Option und das globale YAHOO.js Objekt.
Dieser Artikel ist die deutsche Übersetzung von Christians Beitrag für die Seite 24ways. Dieser Adventskalender bietet noch viele weitere interessante Beiträge, alle in englischer Sprache.
Christian Heilmann lebt seit 1999 im Ausland (USA, England, Indien) und arbeitet derzeit als lei(d|t)ender Entwickler bei Yahoo! in London, England. In seiner Freizeit bloggt er bei wait-till-i.com und schreibt Bücher für Apress und Friends Of Ed.
Bitte die Hausregeln beachten. Alle Kommentare werden auf werbliche Links/Nicknames geprüft und gegebenenfalls gelöscht.
Kommentar-Feed für diesen Beitrag
Die Kommentarfunktion ist zur Zeit leider deaktiviert.
Der beste Artikel in der Serie des diesjährigen Adventskalenders. Sehr anschaulich beschrieben und kann somit leicht nachvollzogen werden.
Kommentar von Webstandard-Team - 18. Dezember 2007 um 08:30
Wer sich weitergehend mit dem Thema beschäftigen möchte, dem sei auch die "Bibel" zum Thema JavaScript ans Herz gelegt: JavaScript. Das umfassende Referenzwerk von David Flanagan. Darin gibt es ein eigenes Kapitel zum Thema Modules und Namensräume. Sehr lesenswert!
Kommentar von Christian Knothe - 18. Dezember 2007 um 08:53
Vielen Dank, Christian, für diesen famosen Beitrag!
Schöne, streßfreie Weihnachten
Kommentar von Carsten Witt - 18. Dezember 2007 um 10:11
Galerie mit 2 "L"…? Könnte man vielleicht noch korrigieren oben im Script.
Kommentar von Daniel - 18. Dezember 2007 um 10:18
Super Beitrag, danke!
Kommentar von David - 18. Dezember 2007 um 12:43
Die Ähnlichkeit zum 18. Kalendertürchen von 24ways fällt doch arg auf. Ein bißchen einfallsreicher wäre es gegangen, auch wenn der Inhalt natürlich sehr gut ist…
http://24ways.org/2007/keeping-javascript-dependencies-at-bay
Kommentar von nikosch - 18. Dezember 2007 um 14:55
Schande über mich. Man sollte die Artikel doch bis zum Ende lesen. Sorry.
Kommentar von nikosch - 18. Dezember 2007 um 14:57