JavaScript-Bibliotheken-Übersicht: jQuery, Dojo, YUI

Walter Christian Kammergruber
JavaScript-Bibliotheken gibt es seit dem Boom um Ajax wie Sand am Meer. Jede hat ihre Stärken und Schwächen. In diesem Artikel werden drei populäre Libraries, nämlich Dojo, jQuery, YUI anhand von Standardaufgaben miteinander verglichen. Ausgehend von diesen Beispielen kann man herausfinden, welche Library am besten für eines neues Projekt, bzw. auch für den individuellen Programmierstil geeignet ist.

Aspekte einer sinnigen JavaScript-Bibliothek

Durch die Verwendung einer JavaScript-Library gestaltet sich die tägliche Arbeit eines Web-Entwicklers wesentlich angenehmer und vor allem auch produktiver. Allerdings stellt sich einem zu Beginn eines neuen Projekts generell die Frage: Welche der tausend verfügbaren Libraries soll man eigentlich verwenden? In dem Artikel wird die Arbeit mit drei etablierten JavaScript-Bibliotheken an einfachen Beispielen gezeigt, nämlich: jQuery, Dojo und YUI. Die Auswahl erfolgte hauptsächlich anhand ihrer Reife und Etabliertheit, d.h. wie umfassend ist eine Library und wie häufig trifft man sie im Netz an.

Gewisse Merkmale gelten als Grundvoraussetzung für moderne und ausgereifte Bibliotheken. Es muss eine browserübergreifende Unterstützung gewährleistet sein. Egal, in welchem der Browser der JavaScript-Code ausgeführt wird, es muss dasselbe Verhalten beobachtbar sein. Bekanntermaßen werden längst nicht alle JavaScript-API-Aufrufe von jedem Browser, bzw. jeder Version eines Browsers, unterstützt. Selbst wenn eine Unterstützung erfolgt, so heißt das noch lange nicht, dass der Aufruf einer gleichnamigen Funktion in unterschiedlichen Browsern das gleiche bewirkt. Als typisches Beispiel gilt XMLHttpRequest, ein zentraler Bestandteil des Ajax-Ansatzes. Diese Schnittstelle hieß im Internet-Explorer einer Version kleiner 7 noch ActiveXObject. Wollte man somit dynamisch Daten über Ajax nachladen, so musste man selbst dafür sorgen, dass die richtige Methode im jeweiligen Browser des Clients aufgerufen wurde. Mit Hilfe einer Library sollte man sich um solche browserspezifischen Merkmale nicht kümmern müssen. Man benutzt dazu eine von der JavaScript-Bibliothek bereitgestellte Methode. Dabei ist diese Kapselung von browserspezifischen Methoden durchaus nicht trivial. Eine browserübergreifende Unterstützung kann sowohl über Feature-Detection als auch über eine allgemeine Erkennung des Browsers erfolgen. Bei ersterem wird überprüft, ob gewisse API-Funktionalitäten von einem Browser unterstützt werden. Bei letzterem erfolgt anhand der Browser-Version ein geeigneter Aufruf. Eine detaillierte Beschreibung, wann welche Methode die beste ist, sprengt offensichtlich den Rahmen dieses Artikels. In einer Diskussion auf Stack Overflow finden sich dazu weiterführende Links [1].

Die Library soll zudem keine nativen JavaScript-Objekte direkt verändern – wie man es beispielsweise vom guten alten Prototype her kennt. Ausnahme hierbei sind Erweiterungen zur Rückwärtskompatibilität von Funktionen. Zugleich soll der globale Namespace so wenig wie möglich verschmutzt werden, d.h. es wird maximal eine globale Variable erzeugt. Implizit wird angenommen, dass geeignete Hilfsfunktionen existieren, wie beispielsweise eine each-Methode, die das Iterieren über eine Reihe von Elementen erleichtert. Eine ausreichend ausführliche und aktuelle Dokumentation darf ebenfalls nicht fehlen. Wenn die Nutzergemeinschaft groß genug ist, so wird man viele Beispiele und Anleitungen im Netz finden.

Was eine gelungene JavaScript-Library ausmacht ist Folgendes: „Mache die einfachen Sachen einfach, die schweren möglich.“ Eine gute JavaScript-Library hilft, den eigenen Code möglichst knapp, simpel und leicht lesbar und daraus resultierend pflegbar zu halten.
Es gibt dabei mehrere entscheidende praktische Kriterien:

  • Wie wird der Umgang mit dem DOM erleichtert?
  • Welche fertigen User-Interface-Element gibt es?
  • Wie einfach kann man die Library mit eigenen Funktionalitäten erweitern?

Die vorgestellten Bibliotheken werden in ihrer jeweils neuesten Version über ein CDN (Content Delivery Network) eingebunden. Folgende Versionen sind im Moment verfügbar: Dojo 1.7.1 [2], jQuery 1.7 [3] und YUI 3.4.1 [4]

Es wird in diesem Artikel immer zuerst ein Grundproblem erklärt und anhand von Beispiel-Code jeweils eine mögliche Umsetzung für die drei Libraries gezeigt. Alle Beispiele liegen mit vollständigem Quellcode auf Github [5].

Arbeiten mit dem DOM

Für die DOM-Beispiele wird von einem einfachen HTML-5-Dokument ausgegangen, das unten aufgeführt wird. Jede Library wird über ein so genanntes Content Delivery Network (CDN) eingebunden. Das hat mehrere Vorteile: Die Library befindet sich eventuell schon im Browser-Cache, da der Besucher auf einer Seite war, die dieselbe Datei aus demselben CDN verwendet hat. Man verwendet einen weiteren Server mit einer anderen Domain (es sind nur eine begrenzte Anzahl von parallelen HTTP-Anfragen für eine Domain erlaubt). Und man muss sich nicht um das Hosting kümmern, was unter Umständen einiges an Bandbreite sparen kann. In der Regel gibt das im Ganzen einen Vorteil für die Ladezeiten einer Page. Im Beispiel wird Dojo über Googles, jQuery über Microsofts und YUI natürlich über Yahoo!s CDN eingebunden.

<!doctype html>
<head>
<meta charset="utf-8">
<title>JS Library Demo</title>
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/dojo.xd.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.min.js"></script>
<script src="http://yui.yahooapis.com/3.4.1/build/yui/yui-min.js"></script>
</head>
<body>
<header> <h1>JS Library Demo</h1> </header>
<div id="main" role="main">
    Eine Liste von wichtigen JS Leuten – nicht vollständig:
    <ul>
        <li><a href="http://brendaneich.com/">Brendan Eich</a></li>
        <li><a href="http://www.crockford.com/">Douglas Crockford</a></li>
        <li><a href="http://ejohn.org/">John Resig</a></li>
    </ul>
</div>
<script>
// hier die weiteren Code-Beispiele einfügen
</script>
</body>
</html>

Finden eines bestimmten DOM-Elements

Zum problemlosen Navigieren innerhalb des DOM kann man mittlerweile auf CSS 3 Selektoren zurückgreifen [6]. Diese werden von allen drei Bibliotheken standardmäßig unterstützt. Somit kann man eine beliebige CSS 3 Referenz für die richtige Syntax zu Rate ziehen. Als einfaches Beispiel dient hier, den Text für das zweite Element einer ul herausfinden – siehe obiges Beispiel-Dokument. Man will also den Text „Douglas Crockford“ als String zurückbekommen.

Mittels CSS 3 Selektoren funktioniert das über folgenden Ausdruck:

#listOfJSPeople>li:nth-child(2)>a:first-child

Zuerst wird nach dem Element mit der ID „listOfJSPeople“ gesucht, davon das 2. Element in den n-Kindknoten von Typ li und davon der erste Kindknoten von Typ a. Was bleibt, ist aus dem Knoten den Text herauszulösen. Das sollte aber über die Library, bzw. mit Standard-JavaScript, kein Problem sein.

Dojo

In Dojo gibt es die Funktion dojo.query

var douglasCrockford = 
    dojo.query("#listOfJSPeople>li:nth-child(2)>a:first-child")[0].innerHTML;
console.log(douglasCrockford);

jQuery

jQuery unterstützt ebenfalls CSS 3 Selektoren. Eine Abfrage erfolgt folgendermaßen:

var douglasCrockford = $("#listOfJSPeople>li:nth-child(2)>a:first-child")[0].innerHTML;
console.log(douglasCrockford);

YUI

YUI ist sehr modular aufgebaut, d.h. man muss genau spezifizieren, welche Module benötigt werden. Für DOM Anfragen benutzt man das Module „node“ und die enthaltenen Funktionen one oder all, d.h. genau ein (das erste) oder alle Treffer. CSS 3 Selektoren sind nicht im Haupt-Modul für DOM-Elemente enthalten, sondern müssen als gesondertes Modul namens „selector-css3“ angefordert werden.

YUI().use('node', 'selector-css3', function (Y) {
    var douglasCrockford =  
        Y.one("#listOfJSPeople>li:nth-child(2)>a:first-child").getContent();
    console.log(douglasCrockford);
});

Erzeugen eines DOM-Elements

Zum Arbeiten mit dem DOM gehört auch die dynamische Erzeugung von DOM-Elementen. Zumeist ist das der Fall, wenn JSON-codierte Daten innerhalb der Seite nachgeladen und gerendert werden sollen. Angenommen man stellt fest, dass bei der Liste der JavaScript-Leute ein paar Namen vergessen wurden, sollte man diese möglichst leicht ergänzen können. Ergänzungen geladener Webseiten um weitere Element sind ein wichtiger Bestandteil für eine fließende Benutzerinteraktion. Twitter und Facebook sind Paradebeispiele für das Nachladen von aktualisierten Informationen. Damit in der Browser-Historie das Vor- und Zurück-Navigieren bei einem dynamischen Seiten-Aufbau funktioniert, wird seit HTML-5 auf die pushState-Funktion zurückgegriffen. Zusätzlich ist es nun erlaubt, die URL dynamisch zu ändern, sofern man sich innerhalb derselben Domain bewegt. GitHub macht von dieser Kombination bei der Navigation durch Quelltexte anschaulich Gebrauch. Für das Beispiel wird auf diese Möglichkeiten der Einfachheit halber verzichtet.

Man bekommt bespielsweise über einen Ajax-Aufruf folgendes Array von Objekten zurück – Personen, die man der Liste hinzufügen will:

var otherJSPeople = [{ "name": "Paul Irish", "url": "http://paulirish.com" }, 
    { "name": "Ryan Dahl", "url": "http://tinyclouds.org/" }, 
    { "name": "Dave Herman", "url": "http://www.ccs.neu.edu/home/dherman/" }];

Für die Beipiele wird davon ausgegangen, dass ein Array otherJSPeople als Variable existiert, das mit den entsprechenden Personen-Objekten gefüllt ist. Mittels der jeweiligen Bibliothek soll die ungeordnete Liste um die weiteren Personen ergänzt werden. Auf ausgefeilte Templating-Mechanismen wird hier der Kürze halber nicht zurückgegriffen.

jQuery

In jQuery wird eine each-Funktion in Kombination mit einem append verwendet.

$.each(otherJSPeople, function (index, value) {
$("#listOfJSPeople").append("<li><a href='" + value.url + "'>" + value['name'] + "</a></li>");
})

jQuery beherrscht neben append weitere Funktionen zur DOM-Manipulation, beispielsweise prepend oder insertAfter. Diese Funktionen sind sehr hilfreich und intuitiv zu verwenden. Man findet sie in der API-Dokumentation unter dem Stichwort „manipulation“.

Dojo

Dojo enthält eine Methode create. Mit dieser können beliebige DOM-Elemente erzeugt werden und zu einem anderen DOM-Element hinzugefügt werden.

var jsPeople = dojo.byId("listOfJSPeople");
dojo.forEach(otherJSPeople, function (value, index) {
dojo.create("li", {
innerHTML: "<a href='" + value.url + "'>" + value['name'] + "</a>"
}, jsPeople);
});

Es wird mittels forEach für jede Person ein neues Listen-Element erzeugt und als Kind von jsPeople angehängt.

YUI

YUI stellt eine Methode insert zur Verfügung, die es erlaubt, zu einem vorher gesuchten Element ein weiteres Element hinzuzufügen.

YUI().use('node', function (Y) {
var jsPeople = Y.one("#listOfJSPeople");
Y.each(otherJSPeople, function (value, key, currentObject) {
jsPeople.insert("<li><a href='" + value.url + "'>" + value['name'] + "</a></li>"); });
});

User-Interface-Komponenten

Wer jemals versucht hat, etwas komplexere User-Interface-Bausteine (UI-Bausteine) von Grund auf neu zu erstellen – auch so, dass diese produktreif in allen Browsern funktionieren – wird wissen, welche Qualen zumeist dahinter stecken und wie lange eine solche Eigenentwicklung dauern kann. Es ist daher ein sehr großer Bonus für eine JavaScript-Bibliothek, wenn sie viele hübsche, durchdachte, leicht zu benutzende und wohl getestete UI-Komponenten bereitstellt. jQuery bietet im Gegensatz zu Dojo und YUI von sich aus keine vorgefertigten UI-Komponenten an. Allerdings gibt es das jQuery-UI-Projekt [7], das diese Lücke schließt. Im Grunde ist die UI bei Dojo auch vom Kern getrennt. Dojo ist unterteilt in drei Unterprojekte: Dojo, Dijit, Dojox. Dojo ist der Kern der Library mit den Standardfunktionalitäten wie Loader oder Hilfsfunktionen, etwa Iteratoren oder Ajax-Methoden. Dijit ist die UI-Library, die Widgets und andere UI-Funktionalitäten zur Verfügung stellt. Dojox fasst stabile Erweiterungen zusammen, die (noch) nicht in Dojo oder Dijit übernommen wurden, unter anderem Incubator- oder experimenteller Code. Bei YUI findet sich keine Gruppierung von Modulen in UI-Komponenten und andere Funktionalitäten.

Ein Autocompletion-Textfeld

In diesem Beispiel soll ein Textfeld um eine einfache Auto-Vervollständigungsfunktion erweitert werden, wie man sie beispielsweise von Googles Diensten wie Mail oder Suche her kennt. In den nachfolgenden Beispielen wird von einem Array mit einschlägigen Programmiersprachen ausgegangen:

var progLangArray = ['C', 'Java', 'C++', 'PHP', 'JavaScript', 'Python', 'C#', 'Perl',
    'SQL', 'Ruby', 'Shell', 'Visual Basic', 'Assembly', 'Actionscript', 'Objective C'];

Ein Nutzer soll beim Tippen in einem Textfeld passende Vorschläge bekommen.

jQuery

Für jQuery gibt es im jQuery-UI-Projekt ein Plugin, das die gewünschte Funktionalität anbietet. Dazu wird zunächst ein Element aus dem DOM selektiert und mittels der Methode autocomplete um die Autovervollständingsfunktionalität erweitert. Es wird hierbei ein Objekt mit den beiden Feldern source und select übergeben. source stellt die Daten bereit. select weist auf eine Funktion, die aufgerufen wird, wenn ein Element aus der Liste ausgewählt wurde. In dem Beispiel wird ein alert mit der Auswahl erzeugt.

<!doctype html>
<head>
    <title>jQuery AutoCompletion Demo</title>
    <script src="../programming-languages.js"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.min.js"></script>
    <link 
    href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css"
    rel="stylesheet" type="text/css" />
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js">
    </script>
    <script>
        $(document).ready(function () {
            $("input#progLangSelect").autocomplete({
                source: progLangArray,
                select: function (event, ui) {
                    alert("Label: " + ui.item.label + ", Wert: " + ui.item.value);
                }
            });
        });
    </script>
</head>
<body>
    <label for="progLangSelect">Welche Programmiersprache?</label>
    <input id="progLangSelect" name="progLangSelect">
</body>
</html>  

Dojo

In Dijit gibt es standardmäßig keine Autovervollständigungs-UI-Komponente für ein Eingabefeld, lediglich eine UI-Element mit dieser Funktionalität für ein Drop-Down-Menu. Da dieses im Grunde genommen eine analoge, wenn nicht sogar gleiche Aufgabe hat, wird sie hier als Beispiel verwendet.

Es gibt zudem keine geradlinige Methode, um das Array mit den Programmiersprachen als Quelle zu verwenden. Hier wird daher auf einen ItemFileReadStore zurückgegriffen, der sich um die Verwaltung von Daten kümmert. Dazu Bedarf es einer leicht anderen Form, wie die Liste der Programmiersprachen modelliert wird:

var programmingLanguages = { label: 'name',
    items: [{"name": "C"}, {"name": "Java"}, {"name": "C++"}, {"name": "PHP"}, 
        {"name": "JavaScript"}, {"name": "Python"}, {"name": "C#"}, {"name": "Perl"}, 
        {"name": "SQL"}, {"name": "Ruby"}, {"name": "Shell"}, {"name": "Visual Basic"},
        {"name": "Assembly"}, {"name": "Actionscript"}, {"name": "Objective C"}]
};

Nun kann diese folgendermaßen eingebunden werden. Zunächst wird ein dojo.data.ItemFileReadStore mit programmingLanguages als Daten gefüllt. Über ein dijit.form.FilteringSelect mit entsprechenden Initialisierungsparametern wird ein Drop-Down-Menu mit Autovervollständingsfunktionalität erzeugt.

<!doctype html>
<html>
    <head>
        <title>Dojo AutoCompletion Demo</title>
        <script src="../programming-languages.js" ></script>
        <script src="http://ajax.googleapis.com/ajax/libs/dojo/1.6/dojo/dojo.xd.js">
        </script>
        <script type="text/javascript">
            dojo.require("dijit.form.FilteringSelect");
            dojo.require("dojo.data.ItemFileReadStore");
        </script>
        <script type="text/javascript">
            dojo.addOnLoad(function() {
                var progLangStore = new dojo.data.ItemFileReadStore({
                    data : programmingLanguages
                });
                var progLangSelect = new dijit.form.FilteringSelect({
                    store: progLangStore, searchAttr: "name" }, "progLangSelect");
            });
        </script>
        <link rel="stylesheet" type="text/css" href=
        "http://ajax.googleapis.com/ajax/libs/dojo/1.6/dijit/themes/tundra/tundra.css" />
    </head>
    <body class="tundra">
        <label for="progLangSelect">Welche Programmiersprache? </label>
        <input id="progLangSelect" name="progLangSelect">
        <p>
            <button onClick="alert(dijit.byId('progLangSelect').get('value'))">
                Wert in der Liste
            </button>
            <button onClick="alert(dijit.byId('progLangSelect').get('displayedValue'))">
                Dargestellter Wert
            </button>
        </p>
    </body>
</html>

YUI

YUI bietet ein Autocomplete-Eingabefeld als Modul an. Es lässt sich folgendermaßen einbinden:

<!doctype html>
<html>
    <head>
    	<title>YUI AutoCompletion Demo</title>
        <script src="../programming-languages.js" ></script>
        <script src="http://yui.yahooapis.com/3.4.1/build/yui/yui-min.js"></script>
        <script>
         YUI().use('autocomplete', 'autocomplete-highlighters', function (Y) {
              Y.one('body').addClass('yui3-skin-sam');
              Y.one('#progLangSelect').plug(Y.Plugin.AutoComplete, {
                resultHighlighter: 'phraseMatch', source: progLangArray, maxResults: 7
              });
        });
        </script>
</head>
    <body>
        <label for="progLangSelect">Welche Programmiersprache? </label>
        <input id="progLangSelect" name="progLangSelect" value="">
    </body>
</html>

Es wird zu einem über Y.one gefundenen Element ein AutoComplete-Plugin über die Methode Y.plug mit den in ein Objekt gekapselten Konfigurationsparametern verknüpft. Die Option resultHighlighter gibt den Typ des Highlightings an. „phraseMatch“ bewirkt, dass die komplette Anfrage im Ergebnis hervorgehoben wird. Die Quelle wird über source übergeben, dabei kann beispielsweise auch eine JSONP-Schnittstelle übergeben werden. maxResults gibt an, wie viele Treffer maximal angezeigt werden.

Ein Kalender-Popup

Ein Kalender-Widget ist mittlerweile ein Element, das bei der Eingabe von Daten erwartet wird. Dadurch werden Fehleingaben reduziert. Als Beispiel kann die Buchung eines Hotels oder Flugs oder die Reservierung eine Mietwagens dienen. In den folgenden Beispielen soll ein Datum von einem Benutzer ausgewählt und das Ergebnis in einem Span-Element wiedergegeben werden. Ein passendes Widget wird von allen drei Libraries zur Verfügung gestellt.

jQuery

Wiederum wird bei jQuery auf das jQuery-UI-Projekt zurückgegriffen. Ein Kalender-Popup oder auf Englisch "date picker" lässt sich folgendermaßen einbinden:

<!doctype html>
<html>
    <head>
    <meta charset="utf-8">
    <title>jQuery Calendar Demo</title>
    <script src="../programming-languages.js" ></script>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.min.js"></script>
    <link 
     href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" 
     rel="stylesheet" type="text/css"/>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js">
    </script>
    <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js">
    </script>
    <script>
        $(document).ready(function() {
            $("#myCalendar").datepicker({
                onSelect : function(dateText, inst) {
                    $("#selectedDate").text(dateText);
                }
            });
        });
    </script>
    <style>/* eigenes CSS... */</style>
    </head>
    <body>
        <div id="myCalendar"></div>
        Ausgewähltes Datum: <span id="selectedDate"></span>
    </body>
</html>

Über die Methode datepicker wird ein DOM-Element um das Kalender-Widget erweitert. Im Parameter-Objekt ist über den Schlüssel onSelect eine Funktion definiert, die aufgerufen wird, sobald ein Datum ausgewählt wurde. Diese setzt den Inhalt vom Span-Element mit der Id „selectedDate“ auf das gewählte Datum.

Dojo

Im Falle von Dojo wird eine Kalender-Komponente aus Dojox verwendet. Der Code dazu sieht folgendermaßen aus:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Dojo Calendar Demo</title>
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.6.1/dojo/dojo.xd.js">
</script>
<link 
href=
"http://ajax.googleapis.com/ajax/libs/dojo/1.6/dojox/widget/Calendar/Calendar.css" 
rel="stylesheet" type="text/css"/>
<script>
  dojo.require("dojox.widget.Calendar");
  dojo.addOnLoad(function () {
      var cal = new dojox.widget.Calendar({}, dojo.byId("myCalendar"));
      dojo.connect(cal, "onValueSelected", function (date) {
          dojo.byId("selectedDate").innerHTML = dojo.date.locale.format(date, {
              selector: "date", formatLength: "short"
          });;
      });
  });       
</script>
<style>/* eigenes CSS... */</style>
</head>
<body>
<header>
   <h1>Dojo demo</h1>
</header>
  <div id="myCalendar"></div>
  Ausgewähltes Datum: <span id="selectedDate"></span>
</body>
</html>

Beim Dojo-Beispiel wird über dojo.connect ein Eventlistener mit der Kalenderkomponente verknüpft. Für das Event „onValueSelected“ wird eine Callback-Funktion definiert, die den Datumswert in das entsprechende Span-Element schreibt. Das Datum wird vorher mit der Hilfsmethode dojo.date.locale.format in ein adäquates Format gebracht.

YUI

YUI bietet das Modul calendar an. Ein Kalender wird folgendermaßen erzeugt:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>YUI Calendar Demo</title>
        <script src="../programming-languages.js" ></script>
        <script src="http://yui.yahooapis.com/3.4.1/build/yui/yui-min.js"></script>
        <script>
            YUI().use('calendar', 'datatype-date', function(Y) {
                var calendar = new Y.Calendar({
                    contentBox : "#myCalendar", width : '340px',
                    showPrevMonth : true, showNextMonth : true,
                    date : new Date(2012, 00, 01)
                }).render();
                // Eine Referenz zu Y.DataType.Date merken
                var dtdate = Y.DataType.Date;
                // Sich für das selectionChange event registrieren 
                //- ein Datum wurde gewählt
                calendar.on("selectionChange", function(ev) {
                    // es können je nach Einstellungen mehrere Dati gewählt
                    // werden hier wird das erste verwendet
                    var newDate = ev.newSelection[0];
                    // Das Datum formatiert rausschreiben
                    Y.one("#selectedDate").setContent(dtdate.format(newDate));
                });
            });
        </script>
        <style>/* eigenes CSS... */ </style>
    </head>
    <body>
        <div id="myCalendar"></div>
        Ausgewähltes Datum: <span id="selectedDate"></span>
    </body>
</html>

Bei YUI wird zunächst ein Kalender-Widget an entsprechender Stelle erzeugt. Über die Methode on wird der Kalender mit dem „selectionChange“-Event verknüpft. Analog zu den anderen Libraries wird eine entsprechende Funktion übergeben.

Ein Slider

Slider sind sehr nützlich, wenn man einem Benutzer das mühsame Eingeben von einzelnen Werten ersparen will. Typische Anwendungsfälle sind Online-Shops, wobei der Kunde die Möglichkeit hat, das Ergebnisse einer Produktsuche über verschiedene Kombinationen aus Facetten einzuschränken. In den folgenden Beispielen soll der Wert des Sliders nach Auswahl durch einen Nutzer in ein Span-Element übertragen werden.

jQuery

Ein Slider wird über jQuery-UI folgendermaßen erzeugt:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>jQuery Slider Demo</title>
    <script src="../programming-languages.js"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js">
    </script>
    <link type="text/css" rel="stylesheet" 
    href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css"/>
    <script>
        $(document).ready(function () {
            $("#slider").slider({ min: 1, max: 10, 
                change: function (event, ui) { $("#sliderValue").text(ui.value); }
            });
        });
    </script>
    <style>/* eigenes CSS... */</style>
</head>
<body>
    <div id="slider"></div>Ausgewählter Wert:
    <span id="sliderValue"></span>
</body>
</html>

Der Slider wird, sobald das DOM vom Browser komplett erzeugt wurde, an dem DOM-Element mit der Id „slider“ gerendert. Als Konfigurationsparameter werden ein minimaler Wert von 1 und ein Maximum von 10 festgesetzt. Als Voreinstellung sind nur diskrete Werte erlaubt. Zugleich wird eine Callback-Funktion über den Schlüssel „change“ definiert. Diese wird aufgerufen, wenn sich der Wert des Sliders ändert. In diesem Fall wird das Span-Element mit der Id „sliderValue“ auf den gewählten Wert aktualisiert.

Dojo

Mittels Dijit lässt sich ein Slider für Dojo so erzeugen:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Dojo Slider Demo</title>
    <script src="http://ajax.googleapis.com/ajax/libs/dojo/1.6.1/dojo/dojo.xd.js">
    </script>
    <script type="text/javascript">
        dojo.require("dijit.form.Slider");
        dojo.addOnLoad(function() {
            var slider = new dijit.form.HorizontalSlider({
                name : "slider", value : 1, minimum : 1, maximum : 10,
                discreteValues : 10, intermediateChanges : true,
                onChange : function(value) {
                    dojo.byId("sliderValue").innerHTML = value;
                }
            }, "slider");
        });

    </script>
    <link rel="stylesheet" type="text/css" 
    href="http://ajax.googleapis.com/ajax/libs/dojo/1.6/dijit/themes/tundra/tundra.css" />
    <style>/* eigenes CSS... */ </style>
</head>
<body class="tundra">
    <div id="slider"></div>
    Ausgewählter Wert: <span id="sliderValue"></span>
</body>
</html> 

Beim Konstruktor der „Klasse“ dijit.form.HorizontalSlider werden die passenden Werte gesetzt. Über den Parameter discreteValues wird die Anzahl der diskreten Werte festgelegt. In dem Falle genau 10, minimum 1 und maximium 10. Über intermediateChange wird definiert, dass auf Änderungen des Sliders sofort reagiert werden soll. Mittels onChange wird eine Callback-Funktion übergeben, die bei Änderungen des Slider-Werts aufgerufen wird. Der zweite Parameter des Konstruktor gibt die id des Elements an, in dem der Slider erzeugt werden soll.

YUI

YUI besitzt ein Modul "slider", das wie folgt eingebunden wird:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>YUI Slider Demo</title>
        <script src="../programming-languages.js" ></script>
        <script src="http://yui.yahooapis.com/3.4.1/build/yui/yui-min.js"></script>
        <script>
            YUI().use('slider', function(Y) {
                var slider = new Y.Slider({min : 1, 
                    max : 10, value : 1}).render("#slider");
                slider.on("slideEnd", function(event) {
                    Y.one("#sliderValue").setContent(slider.getValue());
                });
            });
        </script>
        <style>/* eigenes CSS... */ </style>
    </head>
    <body class="yui3-skin-sam">
        <div id="slider"></div>
        Ausgewählter Wert: <span id="sliderValue"></span>
    </body>
</html>

Zunächst wird eine Slider mit der gewünschten Konfiguration erzeugt. Ähnlich wie in den vorherigen YUI-Beispielen wird über on ein Event mit einer Callback-Funktion verknüpft. In dem Fall des Sliders hat das gewünschte Event den Namen slideEnd, d.h. der Slide-Vorgang ist zu Ende und der Benutzer hat den Slider „losgelassen“.

Eine eigene Erweiterung, bzw. Plugins/Module für die Library erstellen

Nun wird an einem einfachen Beispiel gezeigt, wie die Libraries individuell erweitert werden können. Dazu wird eine Library um ein Tag-Cloud-Modul erweitert.

Bei einer Tag-Cloud handelt es sich um eine sehr beliebte und einfache Methode, um die wichtigsten Tags einer Applikation, wie z.B. die Tags eines Blogs, grafisch darzustellen. Dabei werden Tags typischerweise alphabetisch sortiert und in einem Rechteck angeordnet ausgegeben. Die verwendete Schriftgröße für den einzelnen Tag ist proportional zu seiner Häufigkeit in der gesamten Tagmenge. Üblicherweise wird hierbei eine lineare Skalierung verwendet.

Angenommen man hat 20 Tag-Objekte mit Bezeichnung (name) und entsprechender Häufigkeit (freq). Diese Liste könnte die Tags eines Blogs enthalten, das sich mit Web-Entwicklung beschäftigt. Das Tag mit den Namen „programming“ wurde zum Beispiel 52-mal für Einträge verwendet.

var tags = [
	{"name" : "javascript", "freq" : 80},
	{"name" : "programming", "freq" : 52},
	{"name" : "js", "freq" : 45},
	{"name" : "web", "freq" : 20},
	{"name" : "technology", "freq" : 44},
	{"name" : "script", "freq" : 13},
	{"name" : "git", "freq" : 31},
	{"name" : "webdesign", "freq" : 38},
	{"name" : "tools", "freq" : 43},
	{"name" : "photoshop", "freq" : 10},
	// usw. siehe Code auf github
];

Ohne Verwendung einer Library wird nun von folgendem Basis-Code ausgegangen:

/**
 * @param tags ein array mit Tag-Objekten mit jeweils
 *        zwei Attributen: name der Name und freq die Häufigkeit
 * @param maxFontSize optional die Schriftgöße des häufigsten Tags
 * @param minFontSize optional die Schriftgröße des wenigsthäufigsten Tags
 */
var TagCloud = function () {
        // maxFontSize und minFontSize sind optional
        var maxDataValue = Number.MIN_VALUE, minDataValue = Number.MAX_VALUE,
            maxFontSize, minFontSize, i = 0, numberTags = 0,
            tagName1, tagName2, denominator, curTagFreq, resultHTML = "";
        //  Hilfsfunktion um die Tags alphapethisch zu sortieren
        function tagSortLexical(tag1, tag2) {
             tagName1 = tag1.name.toLowerCase();
             tagName2 = tag2.name.toLowerCase();
             if(tagName1 === tagName2) return 0;
             return tagName1 > tagName2 ? 1 : -1;
        }

        // suche das häufigste und wenigsthäufigste Tag
        function determineMinMaxDenominator(tags) {
            numberTags = tags.length;
            for (; i < numberTags; i += 1) {
                curTagFreq = tags[i].freq;
                if (maxDataValue < curTagFreq) {
                    maxDataValue = curTagFreq;
                }
                if (minDataValue > curTagFreq) {
                    minDataValue = curTagFreq;
                }
            }
            denominator = maxDataValue - minDataValue;
        }

        // Hilsfunktion, um die Schriftgröße abhängig
        // von der Häufigkeit eines Tags zu bestimmen
        function fontSizeLinear(val, maxFontSize, minFontSize) {
            if (denominator === 0) {
                return maxFontSize;
            }
            return Math.round(((val - minDataValue) / denominator) * 
            (maxFontSize - minFontSize) + minFontSize);
        }

        /**
         * Rendert die Tags in einer Tag-Cloud.
         *
         * @param elementId die DOM ID des Elements,
         *    in dem die Tag Cloud gerendert werden soll.
         * @param tags das Array von Tagobjekten mit name und freq
         * @param maxFontSize die maximale Schriftgröße
         * @param minFontSize die minimale Schriftgröße
         */
        this.renderTagCloud = function (elementId, tags, maxFontSize, minFontSize) {
            var maxF = maxFontSize || 30;
            var minF = minFontSize || 10;
            determineMinMaxDenominator(tags);
            // sotiere die Tags alphabetisch
            tags.sort(tagSortLexical);
            resultHTML += "<div class='tagCloud'>";
            for (var i = 0; i < numberTags; i += 1) {
                resultHTML += "<span class='tag' style='font-size: " + 
                fontSizeLinear(tags[i].freq, maxF, minF) + "pt;'>" + tags[i].name + 
                " </span>";
            }
            resultHTML += "</div>";
            document.getElementById(elementId).innerHTML = resultHTML;
        }
}

Die Kommentare sind in der JSDoc-Notation, die an JavaDoc angelehnt ist. Es wird eine Konstruktor-Funktion TagCloud für den entsprechenden Objekttyp definiert. Auf den „prototype“-Mechanismus wird hier der Lesbarkeit halber verzichtet. Performance-Nachteile sind dabei nicht zu erwarten. Der Compiler bzw. Interpreter sorgt für ein entsprechende Optimierung.

Die Berechnung der Tag-Cloud erfolgt über folgende Schritte, die beim Aufruf von renderTagCloud ausgeführt werden:

  1. Bestimmung des häufigsten und am wenigsten häufigen Tags
  2. Sortierung des Tag-Arrays in alphabetischer Ordnung
  3. Iterieren über das sortierte Tag-Array und Erzeugen des HTML-Codes für das entsprechende Span-Element mit der berechneten Schriftgröße als CSS-Eigenschaft
  4. Den HTML-Code in das DOM-Element mit der übergebenen ID schreiben

Die Schriftgröße ist abhängig von der übergebenen maximalen ("maxFontSize") respektive minimalen ("minFontSize") Schriftgröße. Das häufigste Tag erhält die maximale Schriftgröße, das seltenste die minimale Schriftgröße. Zwischenwerte werden linear skaliert.

Eingebettet wird der Code in folgendes HTML-Dokument:

<!doctype html>
<head>
    <meta charset="utf-8">
    <title>Tag Cloud Plain</title>
    <script src="../tags.js" ></script>
    <script src="font-size-util-plain.js" ></script>
    <style type="text/css">
		#tag-cloud {
			width: 300px; margin: 10px; padding: 10px; border: 2px dotted #999;
		}
    </style>
</head>
<body>
    <div id="container">
        <header> <h1>Tag Cloud demo plain</h1> </header>
        <div id="tag-cloud" role="main"></div>
        <script>
            new TagCloud().renderTagCloud("tag-cloud", tags, 10, 30);
        </script>
</body>
</html>

Der obige Code wird für die weiteren Implementierungen als Basis verwendet. Komentare zu den ursprünglichen Funktionen treffen auch auf die angepassten Funktionen zu und werden der Kürze und damit Übersichtlichkeit halber weggelassen.

jQuery

jQuery lässt sich relativ einfach erweitern. Dies geschieht über $.fn.erweiterungsname, womit jQuery um eine Methode ergänzt werden kann.

(function($) {
    $.fn.tagCloud = function(options) {
        var that = this, maxDataValue = Number.MIN_VALUE, 
        minDataValue = Number.MAX_VALUE, i = 0, 
        numberTags = 0, tagName1, tagName2, denominator, curTagFreq, resultHTML = "";
        // enthält die Einstellungen default-Werte werden durch options überschrieben
        var settings = $.extend({ 'maxFontSize' : 10, 'minFontSize' : 30, 
            'tags' : [] }, options);
       
        function tagSortLexical(tag1, tag2) { // siehe Ausgangsbeispiel
        }

        function determineMinMaxDenominator(tags) {
            numberTags = settings.tags.length;
            for(; i < numberTags; i += 1) {
                curTagFreq = settings.tags[i].freq;
                if(maxDataValue < curTagFreq) {
                    maxDataValue = curTagFreq;
                }
                if(minDataValue > curTagFreq) {
                    minDataValue = curTagFreq;
                }
            }
            denominator = maxDataValue - minDataValue;
        }

        function fontSizeLinear(val) {
            if(denominator === 0) {
                return maxFontSize;
            }
            return Math.round(((val - minDataValue) / denominator) * 
            (settings.maxFontSize - settings.minFontSize) + 
            settings.minFontSize);
        }

        function renderTagCloud() {
            determineMinMaxDenominator();
            settings.tags.sort(tagSortLexical);
            resultHTML += "<div class='tagCloud'>";
            for(var i = 0; i < numberTags; i += 1) {
                resultHTML += "<span class='tag' style='font-size: " + 
                fontSizeLinear(settings.tags[i].freq) + 
                "pt;'>" + settings.tags[i].name + " </span>";
            }
            resultHTML += "</div>";
            that.html(resultHTML);
        }

        renderTagCloud();
        return this;
    };
})(jQuery);

Eine Einbindung erfolgt folgendermaßen:

<!doctype html>
<head>
    <meta charset="utf-8">
    <title>jQuery Tag Cloud Plugin</title>
    <script src="../tags.js" ></script>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.min.js"></script>
    <script src="jquery-tag-cloud.js"></script>
    <style type="text/css">
        #tag-cloud {
            width: 300px; margin: 10px; padding: 10px; border: 2px dotted #999;
        }
    </style>
    <script>
        $(document).ready(function() {
            $("#tag-cloud").tagCloud({ minFontSize : 10, maxFontSize : 40, 
            tags : tags});
        });
    </script>
</head>
<body>
        <header> <h1>jQuery Tag Cloud Plugin</h1> </header>
        <div id="tag-cloud"></div>
</body>
</html> 

Beim Aufruf von tagCloud zu dem ausgewählten Element wird intern die Funktion renderTagCloud aufgerufen und jQuery mittels this zurückgegeben. Das ist ein bewährtes Pattern, um das Verknüpfen von verschiedenen jQuery-Methoden zu ermöglichen.

Dojo

Dojo unterstützt offiziell seit der 1.6er Version ein Modulsystem Namens AMD (Asynchronous Module Definition) [8]. Die Idee hinter AMD ist, dass Module und ihre Abhängigkeiten asynchron geladen werden können. Durch dieses asynchrone Modell verspricht man sich, insbesondere in einer Browserumgebung, unter anderem Vorteile bezüglich der Performanz und Testbarkeit.

Ein Modul wird durch eine define-Methode gekapselt:

 define(id?, dependencies?, factory);

Die ersten beiden Parameter sind optional. Über id kann optional eine Modul-ID definiert werden. Diese kann sich im Falle von Dojo auch implizit aus dem Pfad zu einer Datei und einer expliziten Modul-Konfiguration ergeben – siehe dazu das HTML-Dokument weiter unten. dependecies enthält ein Array mit Abhängigkeiten, also Module, die für das zu definierende Modul benötigt werden. Das dritte Argument, factory, beinhaltet den eigentlichen Code für das Modul.

Folgendes Beispiel zeigt ein anonymes Modul, das ein Objekt zurückgibt.

// Import all modules in a single location
define(["libs/otherModule"], function (otherModule) {
    // Exportieren des eigenen Modules als Objekt
    return {
        myFunction: function () {
            otherModule.otherExportedFunction() + 1;
        }
    };
});

Es wird das Modul otherModule importiert. Die Funktion otherExportedFunction aus diesem Modul kann daher für die eigene Funktion myFunction verwendet werden.

Für das Tag-Cloud-Beispiel wird die ID implizit durch den Dateinamen festgelegt. Die Datei heißt TagCloud.js und liegt unterhalb ./demo/util/. Abhängigkeiten gibt es keine und es resultiert folgende Modul-Factory:

define(function() {
    return function() {
        var maxDataValue = Number.MIN_VALUE, minDataValue = Number.MAX_VALUE, maxFontSize, 
        minFontSize, i = 0, numberTags = 0, tagName1, tagName2, denominator, curTagFreq, 
        resultHTML = "";
        function tagSortLexical(tag1, tag2) { // siehe Ausgangsbeispiel
        }

        function determineMinMaxDenominator(tags) {
            numberTags = tags.length;
            for(; i < numberTags; i += 1) {
                curTagFreq = tags[i].freq;
                if(maxDataValue < curTagFreq) {
                    maxDataValue = curTagFreq;
                }
                if(minDataValue > curTagFreq) {
                    minDataValue = curTagFreq;
                }
            }
            denominator = maxDataValue - minDataValue;
        }
       
        function fontSizeLinear(val, maxFontSize, minFontSize) {
            if(denominator === 0) {
                return maxFontSize;
            }
            return Math.round(((val - minDataValue) / denominator) * 
            (maxFontSize - minFontSize) + minFontSize);
        }

        this.renderTagCloud = function(elementId, tags, maxFontSize, minFontSize) {
            var maxF = maxFontSize || 30;
            var minF = minFontSize || 10;
            determineMinMaxDenominator(tags);
            // sotiere die Tags alphabetisch
            tags.sort(tagSortLexical);
            resultHTML += "<div class='tagCloud'>";
            for(var i = 0; i < numberTags; i += 1) {
                resultHTML += "<span class='tag' style='font-size: " + 
                fontSizeLinear(tags[i].freq, maxF, minF) + "pt;'>" + 
                tags[i].name + " </span>";
            }
            resultHTML += "</div>";
            document.getElementById(elementId).innerHTML = resultHTML;
        }
    }
});

In der Factory wird eine Konstruktor-Funktion für ein Tag-Cloud-Objekt übergeben. Von diesem Objekt ist nur die Funktion renderTagCloud von außerhalb sichtbar.

Das zugehörige HTML-Dokument hat folgende Form:

<!doctype html>
<head>
    <title>Dojo Tag Cloud Plugin</title>
    <script src="../tags.js" ></script>
    <script type="text/javascript">
        var dojoConfig = {
            async : true,
            packages : [{
                name : "demo",
                // ein kleiner Hack, um an den *obligatorischen* absoluten Pfad zu kommen
                location : location.pathname.replace(/\/[^/]+$/, "") + "/demo" 
            }]
        };
    </script>
    <script type="text/javascript" 
    src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/dojo.js"></script>
    <script>
        require(["demo/util/TagCloud", "dojo"], function(Tagcloud, dojo) {
            dojo.addOnLoad(function() {
                new Tagcloud().renderTagCloud("tag-cloud", tags, 36, 12);
            })
        });
    </script>
    <style>
        #tag-cloud {
            width: 300px; margin: 10px; padding: 10px; border: 2px dotted #999;
        }
    </style>
</head>
<body>
    <header> <h1>Dojo Tag Cloud Plugin</h1> </header>
    <div id="tag-cloud"></div>
</body>
</html> 

Zu beachten ist, dass bei der Dojo-Konfiguration ein Modul mit Namen "demo" auf ein Verzeichnis relativ zur Domain gesetzt wurde, also einen zum Domainnamen absoluten Pfad. Ein absoulter Pfad ist seit der 1.7er Version verpflichtend. Durch die Konfiguration werden Komponenten, die zum Modul "demo" gehören, in dieser Verzeichnisstruktur gesucht.

YUI

YUI hat ähnlich wie Dojo ein ausgefeiltes Modul-System. Über YUI.add wird einem Modul weitere Funktionalität hinzugefügt. Als erster Parameter wird der Modul-Namespace übergeben. Als zweiter Parameter wird die gewünschte Umsetzung übergeben. Der dritte Parameter ist die Versionsnummer, im Beispiel mit 0.1 angeben. Als letzter Parameter werden Details, unter anderem Abhängigkeiten, festgelegt.

YUI.add('tagcloud-plugin', function(Y) {
    Y.namespace('Examples').TagCloudPlugin = Y.Base.create("TagCloudPlugin", 
    Y.Plugin.Base, [], {
        initializer : function() {
            var div = this.get("host"), tags = this.get("tags"), 
            maxDataValue = Number.MIN_VALUE, minDataValue = Number.MAX_VALUE, 
            minFontSize = this.get("minFontSize"), 
            maxFontSize = this.get("maxFontSize"), i = 0, numberTags = 0, tagName1, 
            tagName2, denominator, curTagFreq, resultHTML = "";
            
            function tagSortLexical(tag1, tag2) { // siehe Ausgangsbeispiel
            }

            function determineMinMaxDenominator() {
                numberTags = tags.length;
                for(; i < numberTags; i += 1) {
                    curTagFreq = tags[i].freq;
                    if(maxDataValue < curTagFreq) {
                        maxDataValue = curTagFreq;
                    }
                    if(minDataValue > curTagFreq) {
                        minDataValue = curTagFreq;
                    }
                }
                denominator = maxDataValue - minDataValue;
            }

            function fontSizeLinear(val) {
                if(denominator === 0) {
                    return maxFontSize;
                }
                return Math.round(((val - minDataValue) / denominator) * 
                (maxFontSize - minFontSize) + minFontSize);
            }

            function renderTagCloud() {
                determineMinMaxDenominator();
                // sotiere die Tags alphabetisch
                tags.sort(tagSortLexical);
                resultHTML += "<div class='tagCloud'>";
                for(var i = 0; i < numberTags; i += 1) {
                    resultHTML += "<span class='tag' style='font-size: " + 
                    fontSizeLinear(tags[i].freq) + "pt;'>" + tags[i].name + " </span>";
                }
                resultHTML += "</div>";
                console.log(div);
                div.setContent(resultHTML);
            }
            renderTagCloud();
        },
    }, {
       ATTRS : {
            tags : { value : []}, minFontSize : {value : 10}, maxFontSize : {value : 36}
        }
    });
}, "0.1", {
    requires : ["base", "plugin", "node"]
});

Es wird ein TagCloudPlugin definiert, das unterhalb YUI.Examples als Plugin zur Verfügung steht. initializer wird beim Aufruf von TagCloudPlugin ausgeführt. ATTRS enthält die Parameter mit entsprechend gesetzten Ausgangswerten.

Das zugehörige HTML-Dokument hat folgenden Code:

<!doctype html>
<head>
    <meta charset="utf-8">
    <title>YUI Tag Cloud Plugin</title>
    <script src="http://yui.yahooapis.com/3.4.1/build/yui/yui-min.js"></script>
    <script src="../tags.js" ></script>
    <script src="yui-tagcloud.js"></script>
    <script>
        YUI().use("node", "tagcloud-plugin", function(Y) {
            Y.one("#tag-cloud").plug(Y.Examples.TagCloudPlugin, {
                minFontSize : 10, maxFontSize : 40,tags : tags
            });
        });
    </script>
    <style>
        #tag-cloud {
            width: 300px; margin: 10px; padding: 10px; border: 2px dotted #999;
        }
    </style>
</head>
<body>
    <header>
        <h1>YUI Tag Cloud Plugin</h1>
    </header>
    <div id="tag-cloud"></div>
</body>
</html> 

Das Plugin wird analog zu den anderen YUI-Widgets eingebunden. Als Abhängigkeit muss dazu das Modul tagcloud-plugin angegeben werden.

Fazit

Zusammenfassend lässt sich sagen: Alle drei Libraries sind für den Produktiveinsatz durchwegs geeignet. Sie sind ausgereift und werden auf vielerlei großen Web-Sites eingesetzt. IBM verwendet Dojo in einigen Produkten und ist deshalb auch Hauptunterstützer. jQuery hat viele Freunde, unter anderem Microsoft, und ist am weitesten verbreitet – sofern man den gängigen Webseiten glauben darf, die Statistiken erheben. YUI scheint eine kleinere Community um sich zu scharen. Allerdings spendiert Yahoo! einiges an Ressourcen für die Entwicklung und Pflege. Das merkt man vor allem an der sehr detaillierten Dokumentation. jQuery ist ebenfalls ausführlich dokumentiert, und man findet kleine Code-Beispiele eingebettet in der API-Dokumentation. Diese Beispiele vermisst man bei der API-Dokumentation von YUI. Bei Dojo enthält die Dokumentation an einigen Stellen (noch?) Lücken. So manches ist exzellent beschrieben und mit ausführlichen Bespielen versehen, andere Funktionen sind hingegen sehr bruchstückhaft dokumentiert und man kann so manche Stunde damit verbringen, sich durch Foreneinträge "zu googlen". Frühere Versionen von Dojo waren ziemlich aufgebläht und wirkten eher schwergewichtig. Mit der 1.7er bzw. teilweise schon 1.6er Version wurde Dojo komplett überarbeitet und fühlt sich leichter an als die Vorgängerversionen. Wenn jemand also früher schon einmal mit Dojo gearbeitet hat und wegen der aufgeblähten Code-Basis zu einer anderen Bibliothek gewechselt ist, so könnte die aktuelle Version durchaus wieder einen Blick Wert sein.

Das Arbeiten mit dem DOM wird von allen dreien gleichermaßen vereinfacht. Auch bieten alle drei einen ähnlichen Umfang an UI-Komponenten, wobei es für jQuery sicherlich mehr von der Entwicklergemeinde bereitgestellte Plugins gibt. Das mag daran liegen, dass der Einstieg in jQuery "gefühlt" einfacher ist. Dazu gibt es eine interessante Diskussion (von November 2010, also nicht mehr komplett frisch) bei Quora, bei der John Resig (der Erfinder von jQuery) eine Frage zum Thema, wie YUI sein Image verbessern kann, beantwortet [9]. Er macht zwei Hauptpunkte aus. Der erste Punkt ist die Einfachheit aller Aspekte einer Bibliothek, das schließt die Webseite und Dokumentation mit ein – zusätzlich zum geradlinigen Umgang mit der Library. Als zweiten Punkt nennt er die Community als entscheidenden Faktor. Die Community soll intensiv in die Entwicklung mit eingebunden werden. Dadurch wird mehr Resonanz erzeugt, mehr Entwickler werden beteiligt und letztlich wird auch mehr Werbung für die Bibliothek gemacht.

Um die jeweilige Library zu erweitern, verfolgen alle drei unterschiedliche Ansätze. Erweiterungen von jQuery ergänzen traditionell die globale jQuery-Variable um weitere Funktionen. Dieses Vorgehen ist relativ intuitiv und geradlinig. Bei größeren Projekten hat man allerdings schnell das Problem, dass es typischerweise Abhängigkeiten zwischen einzelnen Plugins gibt, also z.B. ein Widget A von einem weiteren Widget B abhängig ist. Man muss dann selbst dafür sorgen, dass die Plugins in der richtigen Reihenfolge geladen werden. Bei einer größeren Code-Basis kann das ohne ausgefeilte Build-Tools sehr komplex werden. Abhilfe schaffen dabei Modul-Systeme, die sich um das Auflösen von Abhängigkeiten kümmern. Dojo setzt auf AMD und den Loader RequireJS. Die Vorteile von AMD gegenüber anderen Ansätzen werden auf der RequireJS-Homepage [10] ausführlich erläutert. Einer der Hauptgründe ist die erwähnte Verwaltung von Abhängigkeiten. Im Vergleich zu z.B. CommonJS [11] funktioniert AMD besser im Browser-Kontext. YUI hat ein ähnliches Modulsystem wie Dojo und hilft somit ebenfalls, mit Abhängigkeiten umzugehen. Allerdings ist das Modulsystem YUI-spezifisch. Mit AMD versucht man hingegen, einen gemeinsamen Nenner für die Kapselung in Module zu finden. So unterstützt RequireJS mittels AMD beispielsweise auch jQuery. Allerdings gibt es noch nicht viele jQuery-Plugins, die in der AMD Struktur definiert wurden. Die mangelnde AMD-Ünterstützung könnte YUI somit im Moment (noch) als Nachteil ausgelegt werden.

Welche Library man als geeignetste für das individuelle Projekt auswählt, ist letztendlich Geschmackssache. Allerdings lässt sich sagen, dass YUI und Dojo für komplexere Anwendungen, also so genannte richtige Fat-Clients, eine gute Wahl sind. Für kleinere Dinge mit eher leichtgewichtiger Interaktion führt jQuery wahrscheinlich schneller zum gewünschten Ergebnis.

Quellenangaben

  1. „Browser detection versus feature detection“, auf Stack Overflow. stackoverflow.com/questions/1294586/browser-detection-versus-feature-detection
  2. Dojo-Homepage. dojotoolkit.org
  3. jQuery-Homepage. jquery.com
  4. YUI-Homepage. yuilibrary.com
  5. Code der Beispiele des Artikels auf GitHub. github.com/woidda/mag.js-Artikel-1
  6. „Selectors Level 3“, Çelik et al. www.w3.org/TR/selectors/
  7. jQuery-UI-Homepage. jqueryui.com
  8. AMD. github.com/amdjs/amdjs-api/wiki/AMD
  9. „How could YUI3 improve its image compared to jQuery, MooTools, etc.?“ auf Quora. www.quora.com/How-could-YUI3-improve-its-image-compared-to-jQuery-MooTools-etc#ans143293
  10. RequireJS: Why AMD? requirejs.org/docs/whyamd.html
  11. CommonJS: JavaScript Standard Library. www.commonjs.org

Walter Christian Kammergruber Walter Christian Kammergruber hat an der LMU in München und an der University of Queensland in Brisbane (AU) Informatik studiert. Im Moment arbeitet er neben seiner Tätigkeit als Freiberufler an der Vervollständigung seiner Promotion an der TU München. Er versteht sich als Vollblut-Softwareentwickler und beschäftigt sich seit Jahren mit Web-Technologien. Dabei fühlt er sich im kompletten Stack von Data-Mining bis User-Interface-Design zuhause. Seine Forschungen sind auf den Bereich Social Software unter anderem im Unternehmenseinsatz ausgerichtet. Durch Ajax und neuerdings HTML5 ergeben sich mannigfaltige, interessante Interaktionsmöglichkeiten für ursprünglich von Tim Berners-Lee als statisch konzipierte Web-Seiten.