Details
Der Controller
Herz der JS-Pocket-Lib ist die Klasse Controller. Aufgabe eines Controller-Objektes ist es, Events abzufangen und an andere Objekte weiterzuleiten, die das Ereignis verarbeiten.
Mit der Methode registerEvents() werden die Ereignisse festgelegt auf die das Controller-Objekt achten soll. Die Methode erwartet als Parameter die Namen der zu überwachenden Events in Form eines Arrays, also z.B. myController.registerEvents(new Array('click', 'mouseover'));.
Methode registerEvents() der Klasse Controller Quellcode zeigenQuellcode ausblenden
self.registerEvents = function(eventTitles) {
var bodyObj = document.getElementsByTagName("BODY")[0];
var bodyObj = document;
for(var i=0; i<eventTitles.length; i++) {
if(document.addEventListener) {
if(eventTitles[i] == 'unload' && window.onunload != null) {
window.removeEventListener(eventTitles[i], self.distribute, false);
window.addEventListener(eventTitles[i], self.distribute, "true");
} else {
bodyObj.removeEventListener(eventTitles[i], self.distribute, false)
bodyObj.addEventListener(eventTitles[i], self.distribute, "true");
}
} else {
if(eventTitles[i] == 'unload' && window.onunload != null) {
window.detachEvent("on"+eventTitles[i], self.distribute);
window.attachEvent("on"+eventTitles[i], self.distribute);
} else {
bodyObj.detachEvent("on"+eventTitles[i], self.distribute);
bodyObj.attachEvent("on"+eventTitles[i], self.distribute);
}
}
}
}
Die Funktionsweise von registerEvents() ist sehr einfach. Sie fügt dem BODY-Objekt des aktuellen Fensters zu jedem zu registrierenden Event einen Event Listener hinzu - nichts Neues also. Das Besondere ist, daß für alle Events die Methode distribute() des Controller-Objekts als auszuführende Funktion angegeben wird. Der Controller fängt also zunächst alle Ereignisse ab und verteilt sie dann an die Objekte, welche für die eigentliche Reaktion auf die Events zuständig sind.
Leider ist innerhalb der Methode registerEvents() eine unschöne if-Konstruktion notwendig, da der Internet Explorer eigene Methoden zur Event-Verarbeitung hat, die von dem vom W3C vorgeschlagenen Modell abweichen.
- "IEtoW3C.js Compatibility Script"
- Gut dokumentiertes Script, um dem IE die W3C-konforme Event-Verarbeitung "beizubringen". Damit könnte man die Controller-Methode registerEvents() vereinfachen, indem das if-Statement überflüssig wird. Außerdem hat man bei Einsatz dieses Scripts die Möglichkeit, selbstdefinierte Events zu verwenden. Zu beachten sind die Hinweise zum Overhead, den das Script erzeugt.
- "Advanced event registration models"
- Sehr gute Beschreibung der unterschiedlichen Event-Modelle von Microsoft und W3C durch Peter-Paul Koch, den Betreiber der großartigen Website quirksmode.org (englisch).
Sehr einfach ist die Methode registerClient(), die dem Controller-Objekt sagt, an welche Objekte Events weiterzuleiten sind. Diese Methode tut nichts anderes, als das als Parameter übergebene Objekt dem Array clients hinzuzufügen, der einzigen Eigenschaft, die in der Klasse Controller definiert ist.
Methode registerClient() der Klasse Controller Quellcode zeigenQuellcode ausblenden
self.registerClient = function(client) {
self.clients[self.clients.length] = client;
}
Bei dem übergebenen Objekt kann es sich um eine Instanz einer selbstdefinierten Klasse handeln oder aber auch um ein Element aus dem DOM-Baum, also etwa ein Link-Element. Wichtig ist, daß das übergebene Objekt über die Fähigkeit verfügt, auf von Controller weitergeleitete Events zu reagieren. Dazu muß es entsprechende Methoden besitzen, die ganz einfach den Namen des Events tragen, für den sie zuständig sind, also etwa mouseover() oder keyup().
Für die Weitergabe von Events an die Clients, also an die beim Controller registrierten Objekte, ist die Methode distribute() zuständig. Sie wird automatisch aufgerufen, sobald ein registrierter Event im aktuellen Browserfenster auftritt.
Methode distribute() der Klasse Controller Quellcode zeigenQuellcode ausblenden
self.distribute = function(evt) {
var returnValue = true;
try {
for(var i=0; i<self.clients.length; i++) {
if(typeof(self.clients[i][evt.type]) == "function") {
var handleEvt = self.clients[i][evt.type];
returnValueTemp = handleEvt(evt);
returnValue = (returnValue == false)? false : returnValueTemp;
}
}
if(returnValue == false) {
evt.cancelBubble = true;
if(evt.stopPropagation) evt.stopPropagation();
if(evt.preventDefault) evt.preventDefault();
}
}
catch(err) {}
return returnValue;
}
Als Parameter erwartet distribute() das aktuelle Event-Objekt. Innerhalb der Methode wird durch den Array clients iteriert, der alle Objekte enthält, an die das Controller-Objekt Ereignisse weitergeben soll. Für jedes client-Objekt wird geprüft, ob es eine Methode zur Verarbeitung des aktuellen Events zur Verfügung stellt. Ist dies der Fall, so wird die entsprechende Methode aufgerufen.
Abhängig vom Rückgabewert der ereignisverarbeitenden Methode des client-Objekts wird das Weitergeben des Events an andere Objekte unterbunden oder gestattet.
Bei der Methode distribute() stoßen wir zum ersten Mal auf das Problem, das immer dann auftritt, wenn Events innerhalb des Namensraumes einer Klasse behandelt werden sollen. Die Schwierigkeit besteht darin, daß im Kontext eines Events das Schlüsselwort this immer auf das window-Objekt verweist, was dazu führt, daß man in einer Klassenmethode aus dem Namensraum der Klasse herausgeschleudert wird: this verweist plötzlich nicht mehr auf die aktuelle Instanz der Klasse - im Falle der Methode distribute() also nicht mehr auf das Controller-Objekt -, sondern auf window. Und damit stehen plötzlich sämtliche Eigenschaften und Methoden des Controller-Objektes nicht mehr zur Verfügung.
Glücklicherweise gibt es eine Reihe von Entwicklern, die sich bereits eingehend mit diesem Problem befaßt und Lösungen entwickelt haben. Ich selbst bin über den niederländischen Entwickler Marc Wubben auf die Lösung gestoßen, die ich nun einsetze.
- "Novemberborn"
- Website von Marc Wubben (englisch)
Ich hatte mich dabei zunächst für die Technik entschieden, dem function-Objekt der Methoden jeweils eine Referenz auf den richtigen Scope als Eigenschaft zuzuweisen. In der aufgerufenen Methode galt es dann zu prüfen, ob this noch mit dem richtigen Scope übereinstimmt. Falls nicht, dann wird die Methode mittels call() oder apply() auf das richtige Objekt angewendet. Diese Technik führt allerdings zu etwas unelegantem und in Javascript 1.5 nicht mehr standardkonformem Code, der schwer zu lesen ist.
Daher stellte ich Anfang 2006 die JS-Pocket-Lib auf die einfachere Lösung um, eine lokale Variable self in den Klassen einzuführen, welche auf den korrekten Namensraum verweist, und diese Variable self anstatt des Schlüsselwortes this in allen Methoden zu verwenden. Diese Technik macht den Code schlanker und lesbarer.
- "Preserving Scope in JavaScript"
- Beschreibung des scope-Problems und einiger Lösungsansätze (englisch)
- "Closures"
- Der von der JS-Pocket-Lib seit Anfang 2006 verwendete Lösungsansatz (englisch)
Unter Umständen kann diese Technik allerdings wiederum neue Probleme mit sich bringen, die den Internet Explorer betreffen, nämlich ein Speicherleck (memory leak). Beim Einsatz der JS-Pocket-Lib waren diese Probleme aber bisher noch nicht zu beobachten.
- "Event Cache"
- Demos zum memory leak des Internet Explorers und eine Lösung von Marc Wubben (englisch)
Kommentare zu dieser Seite
(Keine Kommentare vorhanden.)
Kommentarfunktion ausgeschaltet
Ich würde eigentlich gerne Benutzern der JS-Pocket-Lib und anderen Entwicklern die Möglichkeit bieten, schnell und bequem Kommentare und Anregungen hier zu hinterlassen, denn so ein Feedback ist sehr wichtig für die Verbesserung der Bibliothek. Da aber bereits nach wenigen Tagen die Kommentarfunktion, die ich eingebaut hatte, von zerstörungswütigen Vollidioten massivst zum Spamming mißbraucht wurde und ich auf der anderen Seite daraus auch keinen Hochsicherheitstrakt machen wollte, habe ich die Kommentarfunktion wieder entfernt. Sorry an alle normalen Besucher und fuck yourself, Spammers!