Mając możliwość stworzenia modelu i widoku dla danych jest się bardzo blisko skontruowaniu w pełni funkcjonalnej aplikacji. Jedną z rzeczy, której może brakować jest nawigacja. Na szczęście Backbone.js umożliwia bardzo intuicyjne nawigowanie wewnątrz aplikacji, na podstawie identyfikatora fragmentu podawanego w adresie po znaku #, dzięki obiektowi Backbone.Router. W tym artykule postaram się dodać taką funkcjonalność na bazie dotychczas stworzonej aplikacji z poprzedniego artykułu.

Chcąc stworzyć router, wystarczy rozszerzyć obiekt Backbone.Router:

define(function(require) {
	var AppRouter = Backbone.Router.extend({
		routes: {
			"": "handleDefault",
			"form": "handleForm",
			"list": "handleList"
		},
		appView: null,
		viewName: "list",
		initialize: function(options) {
			if (typeof options.appView !== "undefined") {
				this.appView = options.appView;
			}
		},
		handleDefault: function() {
			this.handleView("list");
		},
		handleForm: function() {
			this.handleView("form");
		},
		handleList: function() {
			this.handleView("list");
		},
		handleView: function(viewName) {
			this.viewName = viewName;
			if (this.appView !== null) {
				this.appView.render();
			}
		}
	});
	return AppRouter;
});

Obiekt routes mapuje identyfikatory z adresu URL na wywołania konkretnych funkcji routera (domyślnie handleDefault()). Cała reszta to zaimplementowane metody i obsłużenie wykonania ponownego renderowania widoku, przekazanego jako parametr do konstruktora.

Chcąc wykorzystać router, wystarczy stworzyć jego instancję:

this.router = new AppRouter({
	appView: this
});

Ponadto po stworzeniu jego instancji, warto zadbać o to, aby bieżący adres został obsłużony:

Backbone.history.start();

Po czym warto obsłużyć zmianę dodanej własności viewName w widoku aplikacji, aby mieć pewność, że zmiana adresu jest poprawnie obsłużona:

var h1 = document.createElement("h1");
h1.appendChild(document.createTextNode("Widok: " + this.router.viewName));
this.el.appendChild(h1);

Przydatną funkcjonalnością routera jest obsługa parametrów przy definiowaniu obiektu routes. Dzięki temu, możemy sparametryzować nazwę widoku i okroić wielkość definicji routera:

define(function(require) {
	var AppRouter = Backbone.Router.extend({
		routes: {
			"": "handleDefault",
			":viewName": "handleView"
		},
		appView: null,
		viewName: "list",
		initialize: function(options) {
			if (typeof options.appView !== "undefined") {
				this.appView = options.appView;
			}
		},
		handleDefault: function() {
			this.handleView("list");
		},
		handleView: function(viewName) {
			this.viewName = viewName;
			if (this.appView !== null) {
				this.appView.render();
			}
		}
	});
	return AppRouter;
});

Teraz korzystając z działającego routera możliwa jest modyfikacja renderowania widoku głównego:

if (this.router.viewName === "list") {
	this.el.appendChild(usersView.render());
	var a = document.createElement("a");
	a.appendChild(document.createTextNode("Dodaj"));
	a.href = "#form";
	this.el.appendChild(a);
}
if (this.router.viewName === "form") {
	this.el.appendChild(userFormView.render());
}

Aby po zapisie wrócić do listy, wystarczy zmodyfikować obsługę odpowiedniego zdarzenia:

var context = this;
(...)
userFormView.on("saved", function() {
	(...)
	context.router.handleDefault();
});

Analogicznie można obsłużyć pokazywanie formularza edycji:

usersView.on("change", function(user) {
	(...)
	context.router.handleView("form");
});

Tym sposobem możliwe jest zaimplementowanie prostej nawigacji, która nie wymaga przeładowywania strony. Osoby zainteresowane odsyłam do dokumentacji.

Przykładowy projekt: