Klient REST z wykorzystaniem biblioteki Backbone.js - widoki
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: