
O Backbone.js é um framework Javascript que fornece componentes para
melhorar a estrutura de aplicações web. Dentre os componentes,
encontra-se a Collection, que representa um conjunto ordenado de Models e
traz diversos métodos úteis para trabalhar com coleções de dados.
Introdução
No primeiro artigo desta série foi apresentado o framework Backbone.js, seus principais conceitos e aspectos, e uma introdução rápida através de um “Hello World”. No segundo artigo
da série foi apresentada a classe Backbone.View, demonstrando sua
utilização, templates e a construção de uma View para um exemplo simples
de blog. No terceiro artigo, foi apresentada a classe Backbone.Model junto com um simples backend escrito em Sinatra,
possibilitando o trabalho com dados dinâmicos no exemplo do blog, e
também foi modificada a View para suportar o mecanismo de templates Mustache.
Neste quarto artigo vamos apresentar a classe Backbone.Collection com
exemplos práticos, listagem dos métodos disponíveis para se trabalhar
com coleções de dados na Underscore.js, suporte a eventos, integração
com servidor, melhorias no backend Sinatra desenvolvido e implementação
da listagem de postagens do blog.
Backbone.Collection
Uma coleção é um conjunto de dados ordenados. A classe Backbone.Collection
representa uma coleção de Models, fornecendo diversos métodos úteis
para se trabalhar com estes conjuntos além da possibilidade de manipular
eventos que ocorrem em uma coleção.
Para criar uma classe Collection customizada basta utilizar o método extend(properties, [classProperties]) que recebe como parâmetro as propriedades da coleção, assim como o parâmetro opcional classProperties que define parâmetros diretamente no construtor da coleção.
Além de criar uma coleção customizada, é possível definir o atributo model para configurar com qual Backbone.Model a coleção irá trabalhar. Ao se definir um model também é possível trabalhar com estruturas Javascript puras, no formato de hashs, que são convertidas para o model definido.
Uma coleção das postagens do blog pode ser definida da seguinte forma:
var Posts = Backbone.Collection.extend({
model: Post
});
Assim como nas demais classes Backbone, para se definir um construtor para a Backbone.Collection, basta criar um método initialize(). O construtor padrão recebe como parâmetro um conjunto de models e parâmetros opcionais que incluem também um comparator, utilizado para definir a ordenação das coleções, explicado mais a diante.
Internamente na classe Backbone.Collection todos os models são mantidos em um array, definido no atributo models. A melhor prática para se trabalhar com os models é utilizar os métodos manipuladores da classe, porém caso seja necessário acessar diretamente o array de models, este atributo pode ser utilizado.
Assim como na classe Backbone.Model, a classe Backbone.Collection implementa o método toJSON(),
utilizado para definir a notação JSON do objeto. Este método pode ser
utilizado para serializar e persistir uma coleção completa. Este método
está em conformidade com a API JSON Javascript.
var myPosts = new Posts([
{title: "Post um", text: "Conteúdo do Post um"},
{title: "Post dois", text: "Conteúdo do Post dois"},
{title: "Post três", text: "Conteúdo do Post três"},
]);
console.log(JSON.stringify(myPosts));
Underscore.js
Para iterar pela coleção é possível utilizar 28 funções fornecidas
pela biblioteca Underscore.js. Cada função tem um objetivo distinto e a
lista é bem vasta, portanto será apresentada apenas uma tabela com cada
função e na documentação da Underscore.js você pode obter mais
informações sobre cada função individualmente.
chain |
every |
filter |
find |
first |
forEach |
groupBy |
include |
indexOf |
initial |
invoke |
isEmpty |
last |
lastIndexOf |
map |
max |
min |
reduce |
reduceRight |
reject |
rest |
size |
some |
sortBy |
sortedIndex |
shuffle |
toArray |
without |
Manipulando a coleção
A classe Backbone.Collection fornece diversos métodos para se trabalhar com os dados dos models. Estes métodos permitem adicionar ou remover elementos, obter elementos, ordenar, entre outros. O método add(), por exemplo, permite adicionar um model (ou um array de models) à coleção. Ao executar o método add(), o evento “add” será disparado, a menos que o parâmetro {silent: true} seja definido. Para adicionar o model em uma determinada posição da coleção o parâmetro {at: index} pode ser definido. No callback do evento “add” é possível obter o índice em que o elemento foi adicionado no array options.
var posts = new Posts();
posts.on("add", function(model, collection, options) {
console.log("O model " + model.get('title') + " foi adicionado na posição " + options.index);
});
posts.add([
{title: "Post um", text: "Conteúdo do post um"},
{title: "Post dois", text: "Conteúdo do post dois"}
]);
posts.add({
title: "Post tres", text: "Post tres"
}, {
at: 0
});
O método remove() pode ser utilizado para remover um ou mais models de uma coleção. Ele disparará o evento “remove” a menos que o parâmetro {silent: true} seja definido. Ao se definir um callback para o evento “remove”,
o primeiro parâmetro corresponderá ao model sendo removido e o segundo
conterá um array de opções, onde o índice pode ser obtido no atributo
options.index.
var posts = new Posts();
posts.on("remove", function(model, collection, options) {
console.log("O model " + model.get('title') + " foi removido da posição " + options.index);
});
var models = [
{
id: 1, title: "Post um", text: "Conteúdo do post um"
},
{
id: 2, title: "Post dois", text: "Conteúdo do post dois"
}
];
posts.add(models);
posts.remove({id: 1});
Para obter um determinado model da coleção, utiliza-se o método get() e o mesmo recebe como parâmetro o valor do atributo id do model a ser obtido.
var post = posts.get(2);
console.log(JSON.stringify(post));
Outra forma de obter um model da coleção é através de seu atributo client id. No artigo anterior foi explicado que o client id
é um identificador único atribuído pelo Backbone a um objeto que ainda
não foi gravado no servidor. Para obter um model por seu cid, o método getByCid() pode ser utilizado.
posts.add({title: "Post um", text: "Conteúdo do post um"});
var post = posts.getByCid('c8');
console.log(JSON.stringify(post));
Por último, pode-se obter um model através de sua posição no array, com o método at().
Este método é útil quando a coleção está ordenada, caso ela não esteja,
o método irá obter os models na ordem em que foram inseridos.
var post = posts.at(0);
console.log(JSON.stringify(post));
Para adicionar um model no final de uma coleção, pode-se utilizar o método push() que possui os mesmos parâmetros de add().
posts.push({title: "Novo post", text: "Post adicionado"});
O método pop() remove o último model da coleção e retorna-o. Este método recebe os mesmos parâmetros opcionais do método remove().
console.log(posts.length);
postRemoved = posts.pop();
console.log(posts.length);
console.log(postRemoved.get('title'));
Para adicionar um model no início de uma coleção, utiliza-se o método unshift(), definindo os mesmos parâmetros do método add().
posts.unshift({title: "Novo post", text: "Post adicionado"});
O método shift() remove o primeiro model da coleção e retorna-o. Este método recebe os mesmos parâmetros opcionais do método remove().
console.log(posts.length);
postRemoved = posts.shift();
console.log(posts.length);
console.log(postRemoved.get('title'));
Similar a um array nativo do Javascript, a classe Backbone.Collection possui o atributo length, que retorna o número de models contidos na coleção.
console.log("Existem " + posts.length + " postagens na coleção.");
Uma coleção, como dito anteriormente, é um cojunto ordenado de models. O atributo comparator da classe Backbone.Collection
define uma função para manter uma coleção ordenada e por padrão esta
função não está definida. Isso significa que ao se definir um comparator
os models serão inseridos em seus índices corretos no array collection.models. Um comparator pode ser uma função definida com um simples argumento, que é executada pelo método sortBy() da biblioteca Underscore.js, ou como uma função que recebe dois argumentos e é executada pela função sort() do Javascript. Ao definir um comparator utilizado pelo método sortBy(),
o mesmo deverá receber como parâmetro um model e deverá retornar um
valor numérico ou uma string indicando como o model deve ser ordenado
com relação aos demais.
var posts = new Backbone.Collection;
posts.comparator = function(post) {
return post.get('username');
};
posts.add({title: "Postagem 1", text: "Minha postagem", username: "Fernando"});
posts.add({title: "Postagem 2", text: "Minha postagem 2", username: "Guest"});
posts.add({title: "Postagem 3", text: "Minha postagem 3", username: "Fernando"});
console.log(JSON.stringify(posts));
Um comparator utilizado pela função sort() do Javascript
deverá receber dois models e retornar -1 caso o primeiro model deva ser
adicionado antes do segundo, 0 caso os dois sejam equivalentes e 1 caso o
primeiro model deva ser adicionado depois do segundo.
var orders = new Backbone.Collection;
orders.comparator = function(firstModel, secondModel) {
if (firstModel.get('count') < secondModel.get('count'))
return -1;
else if (firstModel.get('count') > secondModel.get('count'))
return 1;
return 0;
};
orders.add({count: 1});
orders.add({count: 3});
orders.add({count: 2});
orders.add({count: 2});
orders.add({count: 5});
orders.add({count: 4});
console.log(JSON.stringify(orders));
Para forçar que uma coleção seja re-ordenada o método sort()
é utilizado. Geralmente este método não precisa ser chamado já que a
função comparator garantirá que a ordenação seja mantida sempre. Ao
executar o método sort(), o evento “reset” será disparado na Backbone.Collection, a menos que o parâmetro {silent: true} seja definido.
var orders = new Backbone.Collection;
orders.comparator = function(firstModel, secondModel) {
if (firstModel.get('count') < secondModel.get('count'))
return -1;
else if (firstModel.get('count') > secondModel.get('count'))
return 1;
return 0;
};
orders.add({count: 1});
orders.add({count: 3});
orders.add({count: 2});
orders.add({count: 2});
orders.add({count: 5});
orders.add({count: 4});
console.log(JSON.stringify(orders));
orders.on("reset", function() {
console.log("Collection reseted");
});
orders.sort();
O método pluck() pode ser utilizado para obter um array de um determinado atributo presente no conjunto de models. Utilizar o pluck() é o equivalente a utilizar o método map() e retornar um único atributo do iterator.
var users = posts.pluck('username');
console.log("The users of the blog are: " + JSON.stringify(users));
Outro método importante para obter valores da coleção é o where(),
que irá retornar um array dos models da coleção que se equivalem aos
atributos e valores definidos. Este método é útil quando se deseja fazer
buscas na coleção. O exemplo abaixo obtém os posts do usuário “guest”.
var guestPosts = posts.where({username: 'Guest'});
console.log("The guest posts are: " + JSON.stringify(guestPosts));
Interagindo com o servidor
Através da classe Backbone.Collection é possível interagir
com dados dinâmicos de um servidor, permitindo manipular uma coleção com
operações de gravação, assim como obter um conjunto de dados. Para
definir o endpoint de uma coleção o atributo URL é configurado.
Um aspecto importante deste atributo é que ao configurá-lo todos os
models da Collection o utilizarão para construir suas URLs individuais.
Para exemplificar isso, considere o código abaixo:
var Post = Backbone.Model.extend({});
var PostList = Backbone.Collection.extend({
url: "/posts",
model: Post
});
var posts = new PostList();
posts.add({
id: 1,
title: "Meu post",
text: "Conteudo"
});
Neste código nota-se que não foi definido nenhum parâmetro relacionado ao endpoint
no model, em contraste com o que foi implementado no artigo anterior.
E, através da URL, o model consegue criar cada endereço para os endpoints de criação, remoção, atualização e obtenção de dados.
var post = posts.get(1);
console.log(post.url());
post.save()
Seguindo o exemplo apresentado acima, a coleção utilizará o endpoint /posts
para obter a listagem das postagens. Como na coleção o atributo URL
está configurado, os models utilizarão este atributo para gerar os
seguintes endpoints:
- POST /posts – Insere um novo Post
- GET /posts/:id – Obtém um Post individual
- PUT /posts/:id – Atualiza o Post atual
- DELETE /posts/:id – Deleta o Post atual
Para sincronizar a coleção com o último conjunto de models do servidor, o método fetch() é utilizado. Assim que a classe receber uma resposta do servidor, todos os dados atuais são zerados a partir do método reset(),
explicado logo abaixo, e os novos dados obtidos são definidos. Este
método recebe como parâmetro um array de opções que pode conter callbacks para sucesso (options.success) e erro (options.error), e ambas callbacks
recebem como parâmetro a coleção em questão e a resposta do servidor. A
resposta do servidor deve ser um array de objetos utilizando a notação
JSON.
posts.on("reset", function() {
console.log("Collection zerada");
});
posts.fetch({
success: function(collection, response) {
console.log("A resposta foi: " + response);
}
});
Caso seja necessário modificar o comportamento padrão de zerar a
coleção, é possível definir no array de opções o hash {add: true}, que
diz à Backbone.Collection que os dados devem ser apenas adicionados aos já existentes.
posts.on("reset", function() {
console.log("Reset");
});
posts.fetch({
add: true,
success: function(collection, response) {
console.log("A resposta foi: " + response);
}
});
Além destes parâmetros opcionais, também é possível definir
parâmetros suportados na API jQuery.ajax. Um exemplo disso é para obter
dados paginados. O código abaixo pode ser utilizado para isso:
posts.fetch({data: {page: 3}});
Utilizar o método fetch() é interessante somente em casos de lazy-loading,
ou seja, quando a coleção não é populada diretamente no carregamento da
página. Caso seja necessário renderizar a página já com dados
populados, pode-se utilizar o método reset(), que irá remover
todos os models atuais da coleção e adicionar os novos models definidos
como primeiro parâmetro do método. No final da execução do método reset()
será disparado um evento “reset” – a menos que o parâmetro opcional
{silent: true} seja definido no método. Um exemplo disso é ilustrado
pela documentação oficial do Backbone.js, onde uma View Rails já popula
uma Backbone.Collection da seguinte forma:
<script>
var Posts = new Backbone.Collection;
Posts.reset(<%= @posts.to_json %>);
</script>
Outro caso útil para o método reset() é chamá-lo sem nenhum parâmetro, que fará com que a coleção seja zerada e nenhum outro model seja adicionado.
posts.reset();
Quando é feita a requisição de uma ou mais postagens, a API retornará
um objeto JSON crú, sem tipagem. Cabe ao Backbone verificar os
atributos do objeto JSON e mapeá-los a um Model da aplicação. Na classe Backbone.Collection o método parse()
é responsável por este mapeamento. Ele recebe como parâmetro a resposta
em notação JSON e retorna um array de models. Este método é utilizado
automaticamente quando o método fetch() é executado. Caso exista a necessidade de modificar este mapeamento é possível sobrescrever o método parse(), porém este método passará a ser executado para todas as requisições GET da coleção em questão.
var PostsWithRoot = Backbone.Collection.extend({
// A resposta é no formato {posts: []}
parse: function(response) {
return response.posts;
}
});
Para criar uma nova instância de um model em uma coleção, pode-se utilizar o método create(), que
recebe como parâmetro um hash de atributos/valores ou um objeto model
instanciado e não sincronizado com o servidor. Este método fará o
equivalente a instanciar um novo model a partir do hash ou utilizar o
model instanciado passado como parâmetro, gravá-lo no servidor, e
adicioná-lo ao conjunto de models após ele ter sido gravado com sucesso.
O método irá retornar o model criado ou false, caso ocorra algum erro
de validação. A principal condição para que o create() funcione é que o atributo model da classe Backbone.Collection esteja definido corretamente.
Ao criar o model, o evento “add“ será disparado
imediatamente na coleção, e o evento “sync” será disparado assim que o
model for criado com sucesso no servidor. Para fazer com que o model só
seja adicionado à coleção quando for gravado com sucesso, basta definir o
parâmetro {silent: true}.
var Posts = Backbone.Collection.extend({
url: "/posts",
model: Post
});
var posts = new Posts();
var newPost = new Post({
title: "Título do novo post",
text: "Conteúdo do novo post"
});
posts.create(newPost);
Eventos
Ao longo do artigo foram apresentados diversos métodos que a classe Backbone.Collection
oferece. Com eles é possível adicionar um ou mais models a um conjunto
de dados, remover models, limpar o conjunto, sincronizar com o servidor,
gravar no servidor, entre outras operações. Cada operação disparará um
ou mais eventos. Para resumir, uma coleção poderá disparar os seguintes
eventos:
- add: Quando um model for adicionado ao conjunto de models;
- sync: Quando a Collection sincronizar os dados com o servidor;
- reset: Quando uma Collection for limpada;
- remove: Quando um ou mais models forem removidos do conjunto de dados;
- change: Quando ocorrer alteração na Collection.
Tratar estes eventos é um ponto chave em aplicações Backbone. Eles
auxiliarão a manter o estado da View sempre atualizado, mostrando ao
usuário exatamente como estão os models atualmente. Apesar de no
Backbone.js não existir uma maneira fácil de fazer o binding de atributos e View, trabalhar com eventos e callbacks é uma solução aceitável e que dará ao usuário um feedback instantâneo de suas operações.
No restante do artigo será incrementado o blog desenvolvido até
então, permitindo listar as postagens e manipular os models diretamente
na Collection. Também serão adicionados callbacks de eventos para exemplificar o que foi dito até aqui.
Backend
Para o restante do artigo é necessário alterar alguns trechos do código Ruby desenvolvido como backend
no artigo anterior. A API desenvolvida no terceiro artigo não estava
nem um pouco complacente com RESTful, principalmente porque o endpoint
“posts” retornava a última postagem ao invés de todas as postagens.
Mesmo que existam diversos outros fatores necessários para construir uma
API RESTful consistente, como por exemplo a utilização de HATEOAS,
modificar este retorno se torna essencial para que a API respeite mais
os URIs e os métodos HTTP. Portanto o novo backend, denominado posts.rb, contém o seguinte código:
require 'sinatra'
require 'json'
require 'active_record'
ActiveRecord::Base.include_root_in_json = false
class Post < ActiveRecord::Base
end
Post.establish_connection(
:adapter => "sqlite3",
:database => "data.db"
)
# Apresenta a página index.html, que possui o código Backbone.js
get '/' do
File.read(File.join('public', 'index.html'))
end
# Obtém todas as postagens do banco de dados
get '/posts' do
content_type :json
Post.all.to_json
end
# Obtém uma postagem por id
get 'posts/:id' do
content_type :json
post = Post.find params[:id]
post.to_json
end
# Cria uma nova postagem
post '/posts' do
content_type :json
data = JSON.parse request.body.read
post = Post.new
post.title = data['title']
post.text = data['text']
post.save
post.to_json
end
# Atualiza uma postagem existente
put '/posts/:id' do
data = JSON.parse request.body.read
post = Post.find params[:id]
post.title = data['title']
post.text = data['text']
post.save
post.to_json
end
# Remove uma postagem
delete '/posts/:id' do
Post.destroy params[:id]
end
Post.connection.close
É possível notar que, além de modificar o retorno do endpoint /posts, foi adicionado também o endpoint GET /posts/:id, para retornar os dados de uma única postagem, e o endpoint
POST /posts foi modificado para retornar a postagem criada, permitindo
que o Backbone mapeie o atributo id de uma postagem gravada. Para
iniciar o backend Sinatra basta executar o comando abaixo.
ruby posts.rb
Implementando Collection, Model e View
Agora que o backend já está do jeito que é necessário, a
próxima etapa é modificar a aplicação do blog, para fornecer
funcionalidades de listagem, criação e remoção de postagens.
O primeiro passo é modificar o arquivo public/index.html, para adicionar o template de postagem, template de formulário, os arquivos Javascript necessários e inicializar a View principal da aplicação.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Backbone Tutorial - Part 4 Collections</title>
</head>
<body>
<header>
<h1>Blog</h1>
<a href="#" class="add-button">Novo Post</a>
</header>
<section id="content">
</section>
<!-- Templates -->
<script type="text/template" id="post-template">
<h2>{{title}}</h2>
<p>{{text}}</p>
<a href="#" class="remove-button">Remover Post</a>
</script>
<script type="text/template" id="post-form">
<h2>Adicionar Post</h2>
<p><label>Title: <input type="text" id="post-title" /></label></p>
<p><label>Text: <textarea id="post-text"></textarea></label></p>
<p><input type="submit" value="Salvar" /></p>
</script>
<script src="lib/jquery-min.js"></script>
<script src="lib/underscore-min.js"></script>
<script src="lib/backbone-min.js"></script>
<script src="lib/mustache.js"></script>
<script src="js/models/PostModel.js"></script>
<script src="js/collections/PostList.js"></script>
<script src="js/views/PostView.js"></script>
<script src="js/views/PostFormView.js"></script>
<script src="js/views/AppView.js"></script>
<script>
$(function() {
var application = new AppView();
});
</script>
</body>
</html>
A estrutura de pastas do Javascript foi modificada para que contenha
uma pasta para models, uma para Collections e uma para Views. É uma boa
prática para saber exatamente a localização de cada pedaço da aplicação,
mas vale lembrar que não existe uma regra que defina essa estrutura de
diretórios. O model definido continua com o mesmo código do apresentado
no artigo anterior:
var PostModel = Backbone.Model.extend({
defaults: {
title: "",
text: ""
},
validate: function(attrs) {
if (attrs.title == '')
return 'O título é obrigatório';
if (attrs.text == '')
return 'O texto é obrigatório'
}
});
O próximo passo é implementar a classe Collection que irá representar
o conjunto de postagens do blog. Dentro do arquivo
js/collections/PostList.js é criada então uma coleção com o nome
PostsList, definindo também seu atributo url como /posts.
var PostList = Backbone.Collection.extend({
model: PostModel,
url: '/posts',
comparator: function(post) {
-post.get('id');
}
});
var Posts = new PostList();
Observe que já é criada uma instância da Collection. Com isso, o
código básico da aplicação já está criado, faltando agora definir as
Views. Primeiramente existe uma View responsável por apresentar os dados
de uma postagem, esta view é a PostView, definida em
js/views/PostView.js.
var PostView = Backbone.View.extend({
tagName: 'article',
className: 'page-posts',
template: $('#post-template').html(),
events: {
"click .remove-button": "removePost"
},
initialize: function() {
_.bindAll(this, 'render', 'removePost', 'remove');
this.model.on("change", this.render);
this.model.on("destroy", this.remove);
},
render: function() {
var viewContent = Mustache.to_html(this.template, this.model.toJSON());
this.$el.html(viewContent);
return this;
},
removePost: function() {
this.model.destroy();
}
});
O primeiro passo é configurar os parâmetros básicos da View, conforme
já apresentado nos artigos anteriores. A principal diferença aqui é o
bind para o evento “destroy” e o método removePost. O bind fará com que a
View seja removida da apresentação assim que o model indicar, através
do evento “destroy”, que foi excluído. Este evento é executado através
do método model.destroy(), que, irá fazer uma requisição DELETE para o backend.
Falta agora criar uma View para apresentar o formulário de adição de
postagens. Esse é um código um pouco mais extenso, porém sem muitas
diferenças do apresentado no artigo anterior. O arquivo
js/views/PostFormView.js define uma classe PostFormView para esse fim.
var PostFormView = Backbone.View.extend({
tagName: 'form',
className: 'page-form',
id: 'post-form',
attributes: {
action: 'posts',
method: 'POST'
},
events: {
"submit" : "savePost"
},
initialize: function(model) {
_.bindAll(this, 'render', 'savePost');
this.template = $('#post-form').html();
},
render: function() {
var rendered = Mustache.to_html(this.template);
this.$el.html(rendered);
this.titleInput = this.$el.find('#post-title');
this.textInput = this.$el.find('#post-text');
this.hide();
},
savePost: function(e) {
e.preventDefault();
this.model = new PostModel();
this.model.on("error", this.showError);
var title = this.titleInput.val();
var text = this.textInput.val();
this.model.set({
title: title,
text: text
});
if (this.model.isValid()) {
Posts.create(this.model, {wait: true});
this.hide();
Posts.sort();
}
},
hide: function() {
this.$el.hide();
},
show: function() {
this.titleInput.val('');
this.textInput.val('');
this.$el.toggle();
},
showError:function(model, error) {
window.alert('Ocorreu um erro, motivo: ' + error);
}
});
A principal diferença aqui é que o Form é exibido na mesma página das
postagens, em contraste com o código criado no artigo anterior da
série. Ao se trabalhar com Collection fica muito mais fácil de manipular
os models, controlar as Views e os eventos lançados pelas
Collections/Models. Neste caso, por exemplo gravar, uma postagem é tão
simples quanto verificar se os dados são válidos, adicionar o model à
Collection definindo que o evento só será disparado quando o servidor
der uma resposta, ou esconder o formulário. O último passo é criar a
View principal js/views/AppView.js.
var AppView = Backbone.View.extend({
el: $('#content'),
initialize: function() {
_.bindAll(this, 'render', 'addAll', 'addPost', 'showForm');
Posts.bind('add', this.addPost);
Posts.bind('reset', this.addAll);
Posts.bind('sync', this.render);
Posts.fetch();
$('.add-button').on('click', this.showForm);
this.form = new PostFormView();
this.form.render();
$('header').append(this.form.el);
},
render: function() {
this.$el.empty();
this.addAll();
},
addPost: function(post) {
var view = new PostView({
model: post
});
this.$el.append(view.render().el);
},
addAll: function() {
Posts.each(this.addPost);
},
showForm: function() {
this.form.show();
}
});
Esse código é onde todo o trabalho em torno da Collection ocorre.
Primeiro são tratados os eventos add e reset. O add irá criar uma
instância de ViewPost para exibir a postagem ao usuário. O reset irá
varrer todos os Posts, através do método each(), e criar uma nova View para exibição. Esse é todo o código necessário até então.
A aplicação criada será composta então de: listar as postagens,
adicionar uma postagem, remover uma postagem. A primeira tela com a
lista das postagens é apresentada abaixo.

Ao clicar em Novo Post, o formulário é apresentado.

Deixar os campos em branco apresentará um erro de validação.

Preencher corretamente os campos fará com que seja adicionada a nova postagem.

Remover uma postagem fará com que uma requisição DELETE seja excluída do banco e saia da tela de apresentação.

Uma coisa a se destacar nesse código desenvolvido é que foram
necessários alguns truques para exibir/esconder o formulário e remover
uma postagem. Como dito anteriormente, não existe a forma correta ou
errada de se desenvolver aplicações com Backbone.js. Essa é a principal
vantagem deste framework, assim como é a principal desvantagem. Por um
lado isso permite a construção de códigos difíceis de compreender e
dificulta o mapeamento de dependências. Por outro lado, não obriga os
desenvolvedores a fazer de uma única maneira. Nos próximos artigos tudo
isso será desmistificado, e algumas boas práticas serão apresentadas.
Também vale lembrar que ao se construir coleções deve-se avaliar bem os
métodos a utilizar. Como são oferecidos diversos métodos para facilitar a
vida do desenvolvedor, analisar cada um e utilizá-los podem facilitar
bastante o desenvolvimento e entendimento da aplicação.
Código-fonte
O código-fonte de todos os artigos desta séria sobre Backbone.js encontra-se no repositório backbone-tutorial-series do meu GitHub.
Referências
Para a construção deste artigo a documentação do Backbone.js foi utilizada, em conjunto com alguns vídeos do curso de Backbone.js da CodeSchool. Também foi utilizada a documentação do Sinatra, e a documentação do ActiveRecord. Se quiser saber mais sobre HATEOAS clique aqui. Se quiser saber mais sobre os métodos oferecidos pela Underscore.js acesse aqui.
No próximo e penúltimo artigo da série serão apresentadas as classes
Backbone.Router utilizada para construir o roteamento client-side nas
aplicações que utilizam o framework Backbone.js, e Backbone.history
utilizada para guardar os estados de mudança de URLs, assim como a
função Backbone.sync utilizada para ler ou gravar dados no servidor.
***
Artigo de Fernando Geraldo Mantoan