Po zaimplementowaniu modelu danych, potrzebna w aplikacji jest warstwa prezentacyjna, żeby móc zobaczyć dane na jakich działa aplikacja. Do tego służą widoki, które postaram się przybliżyć korzystając z przykładu z poprzedniego artykułu.

Stworzenie widoku sprowadza się do rozszerzenia obiektu Backbone.View i implementacji metody render. Przykładem użycia jest stworzony wcześniej widok główny aplikacji, który póki co sprowadza się do wyrenderowania węzła z tekstem “TODO”:

define(function(require) {
	var AppView = Backbone.View.extend({
		render: function() {
			this.el.appendChild(document.createTextNode("TODO"));
			return this.el;
		}
	});
	return AppView;
});

Samo użycie widoku sprowadza się do stworzenia jego instancji i odpowiednim wywołaniu metody render():

define(function(require) {
	var AppView = require("app/AppView");
	var appView = new AppView();
	$(function() {
		document.body.appendChild(appView.render());
	});
});

Chcąc wyświetlić listę użytkowników z kolekcji, podam przykład stworzenia UsersView. Warto wiedzieć, że domyślnie pole this.el zawiera nowy i pusty znacznik div. Zmienić to można podając jawnie referencję do elementu w konstruktorze widoku lub modyfikując odpowiednie pola w definicji widoku. Chcąc przykładowo wyświetlić listę użytkowników w znaczniku ul, najprościej jest określić pole tagName z odpowiednią nazwą. Ponadto, aby widok miał dostęp do listy użytkowników, warto podać pole collection inicjalizując je w konstruktorze nowy obiektem Users. Rozpoczęcie ładowania listy użytkowników rozwiązać można korzystając z metody initialize(). Ostateczny kod:

define(function(require) {
	var UsersView = Backbone.View.extend({
		tagName: "ul",
		loading: false,
		initialize: function() {
			this.load();
		},
		render: function() {
			while (this.el.firstChild) {
				this.el.removeChild(this.el.firstChild);
			}
			if (this.loading) {
				this.el.appendChild(this.renderLoadingItem());
			} else {
				for (var i = 0; i < this.collection.length; ++i) {
					this.el.appendChild(this.renderUserItem(this.collection.at(i)));
				}
			}
			return this.el;
		},
		renderLoadingItem: function() {
			var li = document.createElement("li");
			li.appendChild(document.createTextNode("Ładowanie..."));
			return li;
		},
		renderUserItem: function(user) {
			var li = document.createElement("li");
			li.appendChild(document.createTextNode(user.get("name")));
			return li;
		},
		load: function() {
			var context = this;
			this.loading = true;
			this.collection.fetch({
				success: function(model, response, options) {
					context.loading = false;
					context.render();
				}
			});
		}
	});
	return UsersView;
});

Wyświetlenie ładującej się listy użytkowników sprowadza się do zmiany metody render() w AppView:

define(function(require) {
	var Users = require("app/model/Users");
	var UsersView = require("app/view/UsersView");
	var AppView = Backbone.View.extend({
		render: function() {
			while (this.el.firstChild) {
				this.el.removeChild(this.el.firstChild);
			}
			var usersView = new UsersView({
				collection: new Users()
			});
			this.el.appendChild(usersView.render());
			return this.el;
		}
	});
	return AppView;
});

Chcąc dodać możliwość dodawania obiektów wystarczy zdefiniować widok formularza:

define(function(require) {
	var UserFormView = Backbone.View.extend({
		tagName: "form",
		render: function() {
			while (this.el.firstChild) {
				this.el.removeChild(this.el.firstChild);
			}
			var context = this;
			var input = document.createElement("input");
			input.name = "name";
			if (this.model.has("name")) {
				input.value = this.model.get("name");
			}
			this.el.appendChild(input);
			var button = document.createElement("button");
			button.type = "button";
			button.appendChild(document.createTextNode("Zapisz"));
			button.addEventListener("click", function() {
				context.model.set("name", document.getElementsByName("name")[0].value);
				context.model.save(null, {
					success: function(model, response, options) {
						alert("Sukces");
					}
				});
			}, false);
			this.el.appendChild(button);
			return this.el;
		}
	});
	return UserFormView;
});

Wyświetlenie formularza sprowadza się do dodania kodu w AppView:

var userFormView = new UserFormView({
	model: new User()
});
this.el.appendChild(userFormView.render());

Po stworzeniu użytkownika powinien pojawić się alert “Sukces”. Zamiast tego warto odświeżyć listę użytkowników i formularz dodawania, żeby zapobiec ponownemu dodaniu tego samego użytkownika. Pomoże w tym mechanizm zdarzeń. Wystarczy zamiast alertu wyzwolić pożądane zdarzenie:

context.trigger("saved");

Po czym przechwycić je w widoku rodzica:

userFormView.on("saved", function() {
	userFormView.model = new User();
	userFormView.render();
	usersView.load();
});

Do zdarzeń można przekazywać dodatkowe parametry. Przykładowo, chcąc dodać przycisk modyfikacji krotki, wystarczy do UsersView dodać kod:

var context = this;
var buttonEdit = document.createElement("button");
buttonEdit.type = "button";
buttonEdit.appendChild(document.createTextNode("Zmień"));
buttonEdit.addEventListener("click", function() {
	user.fetch({
		success: function(model, response, options) {
			context.trigger("change", model);
		}
	});
}, false);
li.appendChild(buttonEdit);

Po czym można parametr użytkownika pobrać w AppView:

usersView.on("change", function(user) {
	userFormView.model = user;
	userFormView.render();
});

Po tych zmianach, formularz dodawania załaduje model użytkownika do zmiany, co pozwoli na jego modyfikację i zapis.

Usunięcie przy takim podejściu jest jeszcze prostsze. Wystarczy dodatkowy przycisk:

var buttonDelete = document.createElement("button");
	buttonDelete.type = "button";
	buttonDelete.appendChild(document.createTextNode("Usuń"));
	buttonDelete.addEventListener("click", function() {
		user.destroy({
			success: function(model, response, options) {
				context.trigger("deleted", model);
			}
		});
	}, false);
	li.appendChild(buttonDelete);

Oraz obsłużenie zdarzenia przez odświeżenie listy:

userFormView.on("deleted", function() {
	usersView.load();
});

Podczas wykonywania warto pamiętać, że https://jsonplaceholder.typicode.com/users jest tylko zaślepką - operacje wykonywane na użytkownikach nie maja faktycznego wpływu na stan serwer. Oznacza to, że wywołania opisane w artykule nie zmienią listy użytkowników i po odświeżeniu listy, nadal będą widoczne te same dane. Jeśli skorzystamy z poprawnie zaimplementowanego API, powinniśmy przekonać się, że zmiany są faktycznie widoczne.

Przykładowy projekt: