Model View Controller mit Backbone.js

Seite 2: Implementierung

Inhaltsverzeichnis

Um Backbone.js zu verwenden, ist zunächst die passende Skriptdatei von der Webseite der Bibliothek herunterzuladen und einzubinden. Da Backbone.js unter anderem für die Vererbung auf Underscore.js zurückgreift, gilt für sie das Gleiche. Darüber hinaus empfiehlt die Dokumentation von Backbone.js den Einsatz von JSON3 und jQuery:

<!doctype html>
<html>
<head>
<title>Backbone.js-Demo</title>
</head>
<body>
<script type='text/javascript' src='json3.min.js'></script>
<script type='text/javascript' src='jquery-2.0.3.min.js'></script>
<script type='text/javascript' src='underscore-min.js'></script>
<script type='text/javascript' src='backbone.js'></script>
</body>
</html>

Anschließend kann man die beiden Klassen Backbone.Model und Backbone.Collection verwenden, um die Domäne mithilfe von Entitäten und Listen zu modellieren. Ausgangspunkt dafür ist stets das Ableiten von einer der beiden Klassen, wobei man die Modellierung in der Regel mit einer Entität beginnt:

var Customer = Backbone.Model.extend({
defaults: {
firstName: '',
lastName: ''
}
});

Die defaults-Eigenschaft definiert die Standardwerte einer neuen Entität. Customer stellt allerdings noch keine solche dar, sondern lediglich deren Blaupause. Um einen konkrete Entität zu erzeugen, muss man Customer als Konstruktor aufrufen. Dabei kann man die Werte der Entität als Parameterobjekt übergeben:

var customer = new Customer({
firstName: 'Golo',
lastName: 'Roden',
age: 34
});

Um auf die Werte zu einem späteren Zeitpunkt wieder zugreifen zu können, stellt Backbone.js die beiden Funktionen get und set zur Verfügung:

console.log(customer.get('firstName')); // => 'Golo'
customer.set('age', customer.get('age') + 1);

Möchte man mehrere Werte gleichzeitig setzen, lässt sich der set-Funktion ein Parameterobjekt übergeben, ähnlich wie beim Aufruf des Konstruktors.

Unabhängig davon, wie die neuen Werte gesetzt werden, benachrichtigt die Entität die Außenwelt über ihre Veränderung: Zum einen löst sie ein allgemeines change-Ereignis aus, zum anderen ein spezielles change:*-Ereignis für jeden einzelnen geänderten Wert. Da Backbone.js jeder Entität über die Basisklasse Backbone.Events eine on-Funktion zur Verfügung stellt, ist es ein Leichtes, auf diese Ereignisse zu reagieren:

customer.on('change:lastName', function (model, value, options) {
// ...
});

Weist man einer Entität einen Wert mit der Bezeichnung id zu, behandelt Backbone.js sie auf besondere Art: Jede Entität verfügt über eine direkte Eigenschaft mit diesem Namen, auf die sich ohne den Umweg über die get-Funktion zugreifen lässt. Außerdem verwendet Backbone.js diese Eigenschaft intern, um eine Entität eindeutig zu identifizieren, beispielsweise innerhalb einer Liste.

Gelegentlich benötigt man statt einer Entität ein reines Datenobjekt. Da Backbone.js mit Ausnahme der Eigenschaft id alle Werte intern kapselt, lässt sich mit der Funktion toJSON eine Entität in ein solches Objekt umwandeln:

var pureData = customer.toJSON();
// => {
// firstName: 'Golo',
// lastName: 'Roden',
// age: 35
// }

Listen von Entitäten funktionieren prinzipiell auf die gleiche Art, man verwendet lediglich die Basisklasse Backbone.Collection statt Backbone.Model. Als Konvention für die Bezeichner von Listen hat sich eingebürgert, ihnen das Suffix List anzuhängen. Als Parameter ist einer Liste die zu verwendende Entität zu übergeben:

var CustomerList = Backbone.Collection.extend({
model: Customer
});

Auch in diesem Fall erhält man als Ergebnis keine konkrete Liste, sondern lediglich eine Blaupause, weshalb auch hier CustomerList als Konstruktor aufzurufen ist. Als Parameter kann man dem Konstruktor eine Liste von Entitäten übergeben, mit denen die Liste initialisiert wird:

var golo = new Customer({ firstName: 'Golo', lastName: 'Roden', age: 34 }),
bill = new Customer({ firstName: 'Bill', lastName: 'Gates', age: 57 });
var customers = new CustomerList([ golo, bill ]);

Analog zu der set-Funktion einer Entität stellt Backbone.js eine gleichnamige Funktion für Listen bereit: Bisher nicht enthaltene Entitäten werden der Liste hinzugefügt, bereits enthaltene werden angepasst – oder entfernt, wenn sie der set-Funktion nicht mehr übergeben werden.

Für einen gezielten Zugriff auf einzelne Listeneinträge bietet Backbone.js darüber hinaus die Funktionen add und remove an. Für das Aktualisieren einer bestehenden Entität muss man keine Funktion aufrufen, es genügt, ihre Werte anzupassen.

Auch in diesem Fall löst Backbone.js verschiedene Ereignisse aus, insbesondere add und remove. Auf sie lässt sich auf dem gleichen Weg zugreifen wie auf die Ereignisse von Entitäten. Die Dokumentation von Backbone.js enthält eine vollständige Liste aller Ereignisse, die Entitäten und Listen auslösen können.

Backbone.js bietet für Listen zudem zahlreiche Funktionen an, um ihren Inhalt zu filtern oder zu sortieren. Dazu zählen insbesondere diverse Funktionen, die aus Underscore.js stammen und von Backbone.js nach außen durchgereicht werden, wie die für zahlreiche Aufgaben ausgesprochen hilfreichen Funktionen map und reduce. Zu guter Letzt verfügen auch Listen über eine toJSON-Funktion, die ihren Inhalt als reines Datenobjekt zurückgibt.

Backbone.js unterstützt es von Haus aus, eigene Logik in einer Entität oder Liste unterzubringen. Hierfür sind keine besonderen Vorkehrungen notwendig. Stattdessen fügt man die gewünschten Eigenschaften und Funktionen der Entität oder der Liste im Aufruf der jeweiligen extend-Funktion hinzu:

var Customer = Backbone.Model.extend({
defaults: {
firstName: '',
lastName:''
},
fullName: function () {
return this.get('firstName') + ' ' + this.get('lastName');
}
});

Besonderes Augenmerk legt Backbone.js darüber hinaus auf das Validieren und Sortieren von Entitäten und Listen. Den wichtigsten Einstiegspunkt für das Validieren von Entitäten stellt die Funktion validate dar, die im Falle einer gültigen Entität undefined, im Fehlerfall jedoch eine entsprechende Fehlermeldung zurückgibt. Auch diese Funktion übergibt man im Rahmen des Aufrufs von extend:

var Customer = Backbone.Model.extend({
defaults: {
firstName: '',
lastName: ''
},
validate: function (attributes, options) {
if (attributes.age < 0) { return 'age must not be negative.'; }
}
});

Die Funktion isValid führt die eigentliche Validierung aus und gibt als logischen Wert zurück, ob die Entität gemäß den in der validate-Funktion hinterlegten Regeln gültig ist oder nicht. Im Fehlerfall kann man dann entsprechend reagieren und den aufgetretenen Fehler mithilfe der Eigenschaft validationError ermitteln:

if (!customer.isValid()) {
console.log(customer.validationError);
}

Zu beachten ist hierbei, dass die set-Funktion keine Validierung auslöst, es sei denn, man übergibt zusätzlich ein passendes Parameterobjekt:

customer.set('age', customer.get('age') + 1, { validate: true });

Für das Sortieren von Entitäten dient in Backbone.js die comparator-Eigenschaft von Listen. Wenn man sie setzt, kümmert sich Backbone.js beim Einfügen von neuen Entitäten in eine Liste automatisch darum, sie sortiert einzufügen, sodass man stets über eine korrekt sortierte Auflistung verfügt.

Um den Wert der comparator-Eigenschaft zu setzen, gibt es drei verschiedene Vorgehensweisen:

  • Im einfachsten Fall weist man ihr den Namen jenes Attributs zu, das man als Sortierkriterium verwenden will. Dieses Vorgehen ist einfach umzusetzen, birgt aber den Nachteil, dass man die Liste ausschließlich nach einem einzigen statischen Kriterium in alphabetischer Reihenfolge sortieren kann.
  • Alternativ kann man der comparator-Eigenschaft eine Funktion zuweisen, die die Entität entgegennimmt und den Namen des Attributs zurückgibt, nach dem man sortieren will. Dabei gelten allerdings die gleichen Einschränkungen wie zuvor. Die einzige Ausnahme stellt die hinzugekommene Möglichkeit dar, das sortierrelevante Attribut dynamisch festzulegen.
  • Als dritte Möglichkeit kann man der comparator-Eigenschaft eine Funktion zuweisen, die zwei Parameter erwartet. Beide Parameter repräsentieren jeweils eine Entität. Auf diesem Weg kann man eine beliebig komplexe Sortierlogik implementieren, wichtig ist einzig der Rückgabewert der Funktion: Der Wert -1 bedeutet, dass die erste Entität vor der zweiten einzusortieren ist; der Wert 1 beschreibt das Gegenteil. Gibt die Funktion hingegen den Wert 0 zurück, bedeutet das, dass beide Entitäten als gleichwertig anzusehen sind.

Nimmt man all dies zusammen, wird klar, dass Backbone.js das Hauptaugenmerk auf die Modellierung der Domäne der Anwendung legt. Bis zu diesem Punkt kann man Backbone.js sogar unabhängig von der Tatsache verwenden, dass es eigentlich zur Implementierung von Single-Page-Anwendungen gedacht ist.