Po stworzeniu szkieletu na podstawie wprowadzenia do biblioteki Backbone.js można zabrać się za implementowanie działającego klienta dla API. Podczas korzystania z architektury REST, najistotniejszy jest model danych i komunikacja pomiędzy klientem i serwerem. Tym razem postaram się przybliżyć w jaki sposób tworzyć modele i kolekcje (zbiory modeli), które pozwolą na operacje na danych udostępnianych przez serwer.

Dla uproszczenia, do celów testowych wykorzystam JSONPlaceholder udostępniający przykładowe zasoby jako serwer REST. Pokażę jak za pomocą przykładowego zasobu https://jsonplaceholder.typicode.com/users tworzyć, pobierać, modyfikować i usuwać użytkowników. Przyda się projekt z wprowadzenia, aby na nim dokonać kolejnych modyfikacji.

Do operacji na pojedynczych użytkownikach wystarczy nam rozszerzenie obiektu Backbone.Model:

define(function(require) {
	var User = Backbone.Model.extend({
		urlRoot: "https://jsonplaceholder.typicode.com/users"
	});
	return User;
});

Widać wyraźnie, że stworzenie prostego modelu jest banalnie proste i wymaga podania zaledwie adresu URL na podstawie którego będą konstruowane odpowiednie żądania (tworzenia, odczytu, modyfikacji i usuwania).

Chcąc stworzyć użytkownika, wystarczy stworzyć nowy obiekt i wykonać metodę save():

var user = new User();
user.save();

Powinno to wyzwolić żądanie POST z zserializowanym jako JSON obiektem użytkownika (w tym wypadku będzie to pusty obiekt {}). Odpowiedź z serwera powinna w przypadku sukcesu zawierać kod statusu 201 (Created) i utworzony obiekt. Na tym jednak, się nie kończą możliwości metody save(). Przy okazji wywołania zapisu, można jako parametr podać obiekt, który ma nadpisać atrybuty, które wskażemy:

var user = new User();
user.save({name: "Jan Kowalski"});

Warto przy tym pamiętać, że zapis jest asynchroniczny, chcąc wykonać jakąś funkcję po poprawnym zapisie lub błędzie, można wykorzystać drugi argument tej metody:

var user = new User();
user.save({name: "Jan Kowalski"}, {
	success: function(model, response, options) {
		console.log("Sukces");
	},
	error: function(model, response, options) {
		console.log("Błąd");
	}
});

Zakładając, że istnieją już jacyś użytkownicy i chcemy pobrać jego dane, wystarczy użyć metodę fetch(). Aby określić, którego użytkownika chciałoby się pobrać, można podać jego identyfikator w obiekcie przekazywanym do konstruktora:

var user = new User({id: 1});
user.fetch();

Spowoduje to spreparowanie żądania GET z doklejonym do adresu urlRoot identyfikatora zasobu “/1”. W odpowiedzi można spodziewać się obiektu JSON z danymi użytkownika ze statusem 200 (OK). Jako, że pobieranie jest asynchroniczne (tak jak zapis), możliwe jest podanie jako argument analogicznego obiektu z metodami zwrotnymi. Chcąc z kolei odczytać pobrane dane, można wykorzystać metodę get(), która przyjmuje jako parametr nazwę atrybutu z modelu:

var user = new User({id: 1});
user.fetch({
	success: function(model, response, options) {
		console.log("Pobrano użytkownika " + user.get("name"));
	}
});

Metoda zwracająca wartość pola ma swój odpowiednik do ustawiania, czyli metodę set(), której należy podać wartość prócz nazwy pola. Chcąc zapisać zmodyfikowany obiekt, można posłużyć się wspomnianą metodą save(), która rozpoznaje, czy zapisywana jest nowa krotka, czy modyfikowana istniejąca:

var user = new User({id: 1});
user.fetch({
	success: function(model, response, options) {
		user.set("name", "Jan Kowalski");
		user.save(null, {
			success: function(model, response, options) {
				console.log("Zmodyfikowano użytkownika " + user.get("name"));
			}
		})
	}
});

W rezultacie, przy zapisie zostanie wyzwolone żądanie analogiczne do żądania tworzącego użytkownika z tą różnicą, że użyta zostanie metoda PUT.

Ostatnią z najważniejszych funkcji wykorzystywanych do działania na obiekcie jest usuwanie. Do usuwania służy metoda destroy(). Podobnie jak wcześniejsze metody, można jej przekazać obiekt z funkcjami zwrotnymi:

var user = new User({id: 1});
user.fetch({
	success: function(model, response, options) {
		user.destroy({
			success: function(model, response, options) {
				console.log("Usunięto użytkownika " + user.get("name"));
			}
		})
	}
});

Wygenerowane żądanie różni się od pobierania pojedynczego użytkownika wykorzystaniem metody DELETE.

Prócz operacji na pojedynczych obiektach, przydatne może być pobranie listy użytkowników. Do tej operacji potrzebujemy kolekcji, czyli zbioru modeli, którą możemy łatwo stworzyć rozszerzając Backbone.Collection:

define(function(require) {
	var User = require("app/model/User");
	var Users = Backbone.Collection.extend({
		model: User,
		url: "https://jsonplaceholder.typicode.com/users"
	});
	return Users;
});

Jak widać, stworzenie kolekcji w najczęstszym przypadku sprowadza się do zdefiniowania adresu URL zasobu i modelu dla pojedynczego elementu (nie jest to wymagane ale przydaje się, jeśli rozszerzymy model o dodatkowe metody). Pobranie listy użytkowników sprowadza się do wykonania analogicznej metody fetch(), co na pojedynczym modelu:

var users = new Users();
users.fetch({
	success: function(model, response, options) {
		console.log("Pobrano " + users.length + " użytkowników");
	}
});

Tak stworzone modele i kolekcje można wykorzystywać z powodzeniem do wszelkiego rodzaju operacji na danych.

Przykładowy projekt: