Backbone.js: Wie der Baukasten für Web-Apps funktioniert
Das Web ist in kontinuierlicher Entwicklung. Es wird immer schneller und die Messlatte der Benutzerfreundlichkeit steigt stetig. Ob Grafikbearbeitung, Musikwiedergabe oder ganze Office-Pakete – Anwendungen, welche man klassischerweise lokal installierte, wandern zunehmend ins Web. Neue Möglichkeiten der verteilten und gleichzeitigen Zusammenarbeit, ständige und ortsunabhängige Verfügbarkeit sowohl der Programme als auch der Daten oder der Verzicht auf mühsame Installationsvorgänge sind nur einige Vorteile.
Um dies möglich zu machen, müssen Entwickler vor allem ein Problem klassischer Websites beheben: das ständige Neuladen der gesamten Site nach der Interaktion durch den Benutzer. Flash diente sich hierfür bereits vor Jahren als Lösung an. Da Apple jedoch entschied, Flash auf iPhone und iPad nicht zu unterstützen, verlor Flash zunehmend an Bedeutung. Hinzu kamen unzureichende Performance sowie insbesondere die Tatsache, dass Flash eine proprietäre Technologie ist.
Die Alternative kam von findigen Entwicklern, die in JavaScript das Potenzial sahen, Web-Applikationen zu entwickeln, die Desktop-Programmen das Wasser reichen können. JavaScript ist auf jedem Gerät mit modernem Browser verfügbar und wird durch eine offene Instanz (ECMA) standardisiert. Dadurch hat es sich trotz vieler Kritik mittlerweile weltweit auch gegen Konkurrenten wie Silverlight oder Shockwave durchgesetzt. Passend dazu endet Douglas Crockfords Artikel „The World’s Most Misunderstood Programming Language Has Become the World’s Most Popular Programming Language“ mit dem Satz: „It’s better to be lucky than smart.“
JavaScript ist tot, lang lebe JavaScript
Dennoch: Mit der Art und Weise, wie JavaScript mehrheitlich in heutigen Websites Verwendung findet, scheint es beinahe unmöglich, eine ganz auf JavaScript basierende Applikation zu entwickeln, geschweige denn wartbar zu halten. Denn die Daten traditioneller Webseiten befinden sich vom Server als HTML gerendert bereits im DOM, dem Document Objekt Model. Letzteres ist die Schnittstelle, über welche JavaScript auf das HTML zugreifen und es verändern kann.
Die JavaScript-Funktionen bedienen sich beim DOM und generieren daraus dynamische HTML-Elemente (DHTML, Dynamic HTML). Dies führt dazu, dass die Programmlogik stark an das Design gekoppelt ist. Diese Art des Entwickelns ist jedoch sehr fehleranfällig, etwa wenn der Grafiker eine Änderung im HTML vornimmt. In diesem Fall kann das dazu führen, dass die JavaScript-Logik nicht mehr funktioniert. Will man nun mit JavaScript eine Single-Page-Applikation erstellen, kommt noch viel mehr Logik hinzu: Seitenwechsel, HTML-Generierung, Synchronisierung mit dem Server, Speicherverwaltung und die Wahrung der Konsistenz der Daten. Um diesen neuen Aufgaben gerecht zu werden, braucht es eine saubere Architektur, sinnvoll gekapselte Datenstrukturen und vor allem die Entkoppelung des Designs (HTML und CSS) von der Logik (JavaScript). Und genau hier setzen neue JavaScript-Bibliotheken wie Backbone.js an.
Merkmale einer Single-Page-Applikation
Eine Single-Page-Applikationen ist nicht einfach eine vom Server gelieferte HTML-Seite. Vielmehr liefert der Server beim ersten Aufruf eine in JavaScript geschriebene Applikation aus. Diese Applikation rendert das HTML direkt im Browser. Ist die Applikation erst einmal ausgeliefert, kommuniziert sie mit dem Server nur noch, wenn Daten geschrieben oder gelesen werden. Die Kommunikation funktioniert per Ajax, einer asynchronen Funktion zur Datenübertragung. Für den Datenaustausch wird in der Regel JSON (JavaScript Object Notation) oder XML verwendet. JSON ist wie XML ein standardisiertes Format zur Datenübertragung. Es ist jedoch viel kompakter als XML sowie direkt von JavaScript lesbar.
Für das Rendern von HTML-Templates ist traditionell der Webserver zuständig. Da der Webserver die Daten nun aber der Applikation per JSON übergibt, muss die JavaScript-Applikation diese selbst in HTML konvertieren. Für das Templating gibt es eine Vielzahl an JavaScript-Frameworks.
Vom Server wird immer die gesamte Applikation dem Browser geliefert. Für die Darstellung der Seite, auf der sich der Benutzer befindet, ist die JavaScript-Applikation selbst zuständig. Um dies umsetzen zu können, braucht es allerdings Client-seitiges Routing, also das Verbinden einer Route (zum Beispiel eineseite.com/kontakt) mit einer Aktion. In diesem Fall eine JavaScript-Funktion, welche definiert, was unter einem Pfad genau geschehen soll.
Da die Applikation mit dem Server Daten im JSON-Format austauscht, müssen die Daten auch im Browser strukturiert abgelegt werden. Eine häufig verwendete Methode ist, diese im DOM mit versteckten Elementen abzulegen. Eine viel bessere Art allerdings sieht vor, die Daten in verschachtelten JavaScript-Objekten, also entkoppelt vom DOM, zu organisieren.
Da die JavaScript-Applikation die Kontrolle über Änderungen an der Seite hat, also kein Neuladen der gesamten Seite durchgeführt wird, können dem Benutzer nützliche Feedbacks in Form von Meldungen, Farbsignalen oder Animationen einfach und kontrolliert vermittelt werden. Dies hat auch den Nebeneffekt, dass sich eine Single-Page-Applikation viel schneller und dynamischer anfühlt als eine herkömmliche Website.
Backbone.js
Backbone.js versucht, die einzelnen Zuständigkeiten einer Single-Page-Applikation in verschiedene Module zu entkoppeln. Die Entscheidung, welche Teile verwendet werden, liegt beim Entwickler. Backbone.js ist somit eine Bibliothek, kein Framework, und damit vergleichbar mit einem Werkzeugkasten, der Werkzeuge lediglich zur Verfügung stellt. Deren Nutzung obliegt dabei dem Entwickler, für komplexe Aufgaben lassen sich die Werkzeuge kombinieren.
Backbone.js beruht auf dem Bottom-Up-Prinzip: Schritt für Schritt lassen sich Funktionalitäten der Applikation hinzufügen. Dies macht es sehr flexibel und unterscheidet es vor allem von vielen seiner Framework-Konkurrenten, etwa Ember.js und Angular.js. Ein Framework gibt Rahmenbedingungen vor und hat oftmals darauf beruhende Automatismen integriert. Innerhalb dieses Rahmens verkürzt sich häufig die Entwicklungszeit. Passt das angestrebte Produkt jedoch nicht in den vorgesehenen Rahmen, sind häufig zeitraubende Workarounds notwendig. Erweiterungen bestehender Applikationen sind daher mit Frameworks eher schwierig umsetzbar.
Kritik erntete Backbone.js in letzter Zeit wegen seiner minimalistischen Herangehensweise. Viele Entwickler stört es, dass man häufig die gleiche Logik mehrmals programmieren muss. Sie vermissen also genau die Eigenschaften eines Frameworks. Die Tatsache, dass Backbone.js kein Framework ist, muss es jedoch nicht unproduktiv machen. Es kümmert sich einfach nur um Kernfunktionalitäten. Dafür verfügt es über ein ausgeklügeltes Plugin-System: Beim Start eines neuen Projekts ist die Architektur frei wählbar, im Anschluss bieten sich verschiedene Frameworks als Backbone.js-Aufsatz für die Entwicklung an – etwa Backbone.Marionette.
Im laufenden Betrieb benötigt Backbone.js eine Ajax-Bibliothek (zum Beispiel jQuery oder Zepto) sowie die Utility-Bibliothek Underscore.js. Letztere erweitert JavaScript um viele Array- und Objekt-Funktionen. Unter anderem ist es damit möglich, JavaScript-Objekte zu durchsuchen, zu filtern, zu verändern und zu vererben.
Backbone.Events
Events sind in Backbone-Applikationen unumgänglich, denn alle anderen Module erben das Modul Backbone.Events. Dieses basiert auf dem weitverbreiteten Observer-Pattern und ermöglicht es, Funktionen an Events eines Objekts zu binden, die bei Eintreten des Events ausgeführt werden. Dabei lassen sich auch mehrere Funktionen an die gleichen Events anbinden.
Als Beispiel soll eine Person mit einem Megaphon herhalten. Diese Person hat eine Gruppe von Personen vor sich, von dene jede eine Aufgabe hat, die bei Ertönen eines individuellen Schlüsselworts ausgeführt wird. Die einzelnen Personen kennen dabei die Aufgaben der anderen nicht. Sobald aber die Person mit dem Megaphon das Schlüsselwort verkündet, führen alle Personen die vereinbarten Aktionen aus. Dabei kann die Person mit dem Megaphon die Anweisung, ein bestimmtes Schlüsselwort zu verkünden, wiederum von einer anderen unabhängigen Instanz erhalten, die selbst nicht wissen muss, wer alles auf das Schlüsselwort reagiert.
Interessant an dieser Architektur ist, dass jene Funktion, die einen Funktionsaufruf an ein Event anbindet („Binder“), nicht die Funktion sein muss, die die beim Eintreten des Events angebundenen Funktionen ausführt („Trigger“). Binder und Trigger können stattdessen an gänzlich unterschiedlichen Orten innerhalb der Applikation definiert sein – einzig das Objekt, um dessen Event es geht, in der Analogie von eben die Person mit dem Megaphon, müssen beide kennen.
Backbone.Model
Models sind das Herz jeder Single-Page-Applikation. Sie definieren die Datenstrukturen, um die es im Kern der jeweiligen Anwendung geht. Eine Applikation zur Benutzerverwaltung würde beispielsweise ein Model für die Benutzer und eines für verschiedene Benutzertypen definieren. Eine Model-Instanz lässt sich mit einem Eintrag einer Tabelle vergleichen. Durch Models werden Daten als reine JavaScript-Objekte gespeichert. Es ist also nicht mehr nötig, den DOM als Datenspeicher zu missbrauchen.
Backbone.Model erbt von Backbone.Events, es können somit Funktionen an Backbone.Model.Events angebunden werden. Was banal klingt, ist eine wertvolle Unterstützung in der Entwicklung, zumal Funktionen immer so nahe wie möglich an der betroffenen Stelle definiert werden sollen. Somit gehören Funktionen, die sich direkt auf Model-Attribute beziehen, in die Models selbst. Ein Beispiel ist die Benutzerverwaltung von eben: Im Sinne gekapselter Model-Funktionalitäten gehört eine Funktion, die aus den Attributen Name und Vorname den vollständigen Namen generiert, in das Model selbst. Auch sind so genannte „computed columns”, also Berechnungen aus einzelnen Attributen, im Model zu definieren, beispielsweise der Umfang oder die Fläche geometrischer Figuren.
Backbone.Collection
Backbone.Collections sind eine Menge von Models, ähnlich einer Datenbank-Tabelle, wobei sich ein Model in mehreren Collections befinden kann. Die Backbone.Collections verwenden fast alle Underscore.js-Objekt- und -Array-Methoden. So können die Collections bequem gefiltert, durchsucht, sortiert und verändert werden. Die wichtigsten Funktionen dabei sind each, get, at, filter, where, first und last.
In die Collection gehören Funktionen, die mehrere Models betreffen. Im Beispiel der Benutzerverwaltung kann das die Sortierung oder Filterung nach Personenmerkmalen sein. In einem mathematischen Beispiel gehören Algorithmen in die Collection, zum Beispiel eine Ansammlung von Punkten, die jeweils über die Attribute X und Y verfügen. Sollen die Punkte nun in einer Wellenbewegung animiert werden, gehört der entsprechende Algorithmus in die Collection.
Backbone.Sync
Backbone.Sync ist zuständig für die Kommunikation zwischen der Client-seitigen JavaScript-Applikation und dem Server. Backbone.Sync wird von der Backbone.Collection und dem Backbone.Model verwendet, um Daten zum Server zu senden oder von selbigem zu empfangen. Standardmäßig verwendet Backbone.Sync die RESTful-Konvention; dieses Verhalten lässt sich aber beliebig anpassen. Somit ist Backbone.js komplett Backend-agnostisch – es ist also egal, ob der Server-Code in Ruby, Java, PHP, C# oder gar in JavaScript selbst (mit Node.js) geschrieben ist.
Backbone.View
Backbone.View ist die Schnittstelle zwischen der Business-Logik der Models und Collections und der Darstellung im Browser (DOM). Backbone.View besitzt die Funktion „render”, in welcher die HTML-Darstellung der View-Schnittstelle generiert wird. Ob dieser HTML-Code nun durch das Zusammensetzen einzelner String-Komponenten oder durch den Einsatz einer (beliebigen) JavaScript-Template-Engine geschieht, ist dem Entwickler überlassen. Die Render-Funktion lässt sich dann an Model-Change-Events anbinden. So rendert die View automatisch neu, wenn im zugeteilten Model ein Attribut geändert wird. Backbone.View verfügt darüber hinaus über eine Konvention, wie Events an DOM-Elemente gebunden werden. In den Views wird somit eine interaktive Benutzerschnittstelle definiert.
Backbone.Router
Ein weiterer elementarer Bestandteil der Single-Page-Applikation ist das Routing. Dafür sorgt Backbone.js mit dem Backbone.Router, der lediglich auf das Ändern der URL hört und eine darauf registrierte Funktion ausführt. Was diese Funktion macht, ist beliebig; meistens wird damit aber eine Backbone.View gerendert und per jQuery an einer gewünschte Stelle im DOM dargestellt.
Fazit
Moderne Web-Applikationen werden immer komplexer und verlangen entsprechend nach neuen Ansätze in deren Entwicklung. Backbone.js kann dabei behilflich sein. Als JavaScript-Bibliothek drängt es sich dabei nicht auf, wurde es doch mit dem Gedanken entwickelt, einfach anpassbar und erweiterbar zu sein. Außerdem setzte Backbone.js ein Umdenken innerhalb der Web-Community in Gang: Denn statt auf eine stark DOM-gerichtete Entwicklung setzt die Bibliothek auf gängige Technologien klassischer Desktop-Applikationen. Möglich sind diese mit JavaScript schon lange, nur hat sich damit bislang niemand beschäftigt.
good
Cool!
Ein hervorragendes Grundgerüst für Backbone bietet Chaplin: http://chaplinjs.org/
„Für das Rendern von HTML-Templates ist traditionell der Webserver zuständig.“
Das ist nicht korrekt. Der Webserver ist für die Auslieferung der Inhalte zuständig, gerendert wird die Webseite traditionell von der HTML-Engine im Browser, z.B. Webkit.
Hier ein interessantes Beispiel wie man Backbones verwenden kann.
http://blog.mwaysolutions.com/2013/12/20/offlineonline-synchronization-with-bikini-all-a-model-needs/#more-645