Hin und wieder stolpert man bei der Webintwicklung immer wieder über die gleichen Probleme: Sei es eine custom-styled ComboBox, ein RadioButton mit einer benutzerdefinierten Grafik oder etwas ganz anderes. Anstatt nun das Rad immer wieder neu zu entwickeln - sprich: jede verwendete ComboBox neu zu programmieren - kann man bei der Entwicklung mit jQuery den Plugin-Ansatz verfolgen. Dabei erweitert man jQuery um zusätzliche Funktionalität, die man beliebigen jQuery-Objekten zuweisen kann. Nach aussen hin fungiert das Plugin (i.d.R.) als eine Art 'Black Box', die man verwenden kann, ohne näheres über die innere Funktionsweise zu wissen:
// build object
$(".xy").demoTextPlugin( { text : 'Hallo schöne Welt!'} );
$(".xy").demoTextPlugin( 'setText', 'Hallo neue Welt.' );
Im obigen Beispiel werde alle Objekte mit der Klasse xy selektiert. Jedem der Selektierten Objekte wird eine Instanz des demoTextPlugins zugewiesen. In der dritten Zeile wird eine Hilfsfunktion setText aufgerufen, die irgendwelche Funktionalität ausführt.
Nun noch ein paar Worte zu jQuery-Plugins im allgemeinen.
Generell sind mir bisher drei Ansätze für jQuery-Plugins unter die Augen gekommen:
Die erste Art von Plugins bietet dem Programmierer nach der Aktivierung keine Schnittstelle zur weiteren Kommunikation an. In manchen Fällen mag das ausreichend sein, zumeist möchte man dem Plugin aber später Befehle geben können.
Bauart "zwei" interagiert nach dem Aktivieren durch eine einzige Funktion (die dem Konstruktor entspricht) mit dem Plugin.
// build object
$(".xy").demoTextPlugin( { text : 'Hallo schöne Welt!'} );
// send command to object
$(".xy").demoTextPlugin( 'setText', 'Hallo neue Welt.' );
Bauart "drei" bindet weitere Funktionsaufrufe an das jeweilige jQuery Objekt. Die weiteren Funktionen bekommen zumeist einen Präfix:
// build object
$(".xy").demoTextPlugin( { text : 'Hallo schöne Welt!' } );
// send command to object
$(".xy").demoSetText( 'Hallo neue Welt.' );
Dies hat Vor- und Nachteile: Einerseits kann zu Namens Kollisionen kommen, wenn zwei Plugins gleiche Funktionen an ein jQuery Objekt binden wollen. Dem gegenüber steht der Vorteil, dass man die Funktionen sauber von einander getrennt hat und nicht erst zeitraubend aufdröseln muss.
Im folgenden werde ich einen Ansatz für Bauart "drei" liefern. Mit ein wenig Fleiß lässt sich der Code aber auch schnell an Bauart zwei (oder eins durch weglassen aller Interface-Methoden
) adaptieren:
(function ($){
$.fn.extend ({
demoPlugin : function (options)
{
if (!$.event._demoCleanup) $.event._demoCleanup = [];
options = $.extend( {
optionA : 'Hallo',
optionB : ' Welt!'
},
options );
return this.each (function () {
if (!this._demoController) {
this._demoController = new DemoPlugin (this);
$.event._demoCleanup[$.event.guid] = this._demoController;
$.event._demoCleanup[$.event.guid++].init (options);
}
});
},
demoGetText : function ()
{
if (this[0]._demoController)
return this[0]._demoController.getText ();
return false;
},
_demoDestroy : function ()
{
return _eachCall.call (this, 'cleanup');
}
});
var _eachCall = function (f, a1, a2, a3)
{
return this.each (function () {
if (this._demoController)
this._demoController[f](a1, a2, a3);
});
};
$(window).bind('unload', function() {
var els = $.event._demoCleanup || [];
for (var i in els) {
$(els[i].ele)._demoDestroy();
}
});
function DemoPlugin(ele)
{
this.ele = ele;
this.$ele = $(this.ele);
};
$.extend(DemoPlugin.prototype, {
bindToObj: function(fn) {
var self = this;
return function() {
return fn.apply(self, arguments)
};
},
init : function(s)
{
this.optionA = s.optionA
this.optionB = s.optionB;
},
cleanup : function()
{
},
getText : function ()
{
return this.optionA + this.optionB;
}
});
})(jQuery);
Zuerst einmal möchte ich die generelle Idee vorstellen, die dem Skript zugrunde liegt. Gleichzeitig gehe ich auch auf die Struktur des Quelltextes ein:
Das oben gezeigte jQuery-Plugin fügt dem zentralen jQuery-fn-Objekt drei Funktionen hinzu (mit Prefix demo oder _demo). In unserem Fall sind dies der Konstruktor 'demoPlugin' (zum erzeugen einer neuen Plugin-Instanz), der Destruktor '_demoDestroy' (um nach verlassen der Seite wieder aufzuräumen) und eine Service-Funktion 'demoGetText', über die unser Plugin nach aussen mit uns Kommunizieren kann.
Alle Funktionen, die nach aussen sichtbar sind, erhalten das gewählte Prefix (in unserem Falle 'demo'). Ein guter Prefix hilft, Kollisionen mit anderen Plugins, die die gleiche Funktion registrieren (wollen) zu vermeiden. (Dieses Problem betrifft natürlich nur Ansatz 3 - bei Ansatz 2 existiert das Problem naturgemäß nicht.)
Zusätzlich wird noch eine Hilfsfunktion definiert, welche bei vielen Service-Funktionen spürbar den Quelltext verkleinert. Desweiteren wird eine 'unload event'-Funktion zum Aufruf der Destruktoren registriert.
Last but not least wird noch die eigentliche Controller-Klasse definiert. Um das Beispiel nicht unnötig zu verkomplizieren habe ich auf eine separate Modellklasse verzichtet.
Beim Konstruieren einer Plugin-Instanz werden zunächst die übergebenen Optionen mit den Defaults ergänzt (nicht übergebene Einträge werden dabei auf den Default Wert gesetzt). Danach registriert sich das Plugin einmal im lokalen jQuery-Objekt als _demoController und noch einmal zentral in $.event._demoCleanup. Das zentrale Register erlaubt es, in der 'unload event'-Routine effizient alle Instanzen durch Aufrufen des Destruktors (_demoDestory) zu zerstören.
Die weitere Funktionalität ergibt sich aus dem Quelltext direkt und ich gehe an dieser Stelle nicht weiter darauf ein. Bei Bedarf steht die Kommentarfunktion natürlich für Fragen und Anmerkungen zur Verfügung.
Kommentare