Atenção: a didática deste post foi desenvolvida para quem já tem algum conhecimento em Backbone. Se você estiver começando agora, do zero, este artigo não é para você.
***
Howdy, partners! Imagine que você esteja fazendo uma lista de compras no seu site com Backbone.js. Agora, para ilustrar melhor ainda, imagine que você tem algo mais ou menos assim:
Tecnicamente falando, podemos abstrair este simples widget em três camadas:
- O “#products-header”, onde aparece “1 produto adicionado:”;
- O “#products-list”, onde aparece o “Livro de Backbone.js (2)”;
- O “#products-footer”, onde aparece o botão “Adicionar produto”.
Então, com esse modelo de abstração, tenho três views, sendo, respectivamente:
- MyApp.Views.Produts.Header;
- MyApp.Views.Products.List;
- MyApp.Views.Products.Footer.
Agora, vamos analisar as nossas regras:
- Product é um modelo (em português brasileiro) ou model (em inglês);
- Products é uma coleção (em português brasileiro) ou collection (em inglês);
Portanto, Products possui vários Product, o que é facilmente exemplificado no mundo real como uma “Products” sendo uma “lista” de produtos (ou, Product). Products já vive e já é representado através da sua própria view (MyApp.Views.Products.List), ou seja, já sabemos como ele é; mas e um produto em si, como ele se parece? Pois é… Teríamos, por fim, uma quarta view invocada como MyApp.Views.Products.Item. Então, uma lista de produtos pode ter um ou mais produtos – e esse valor é representado através da view MyApp.Views.Products.Header através do “1 produto adicionado” – lembra? Os envolvidos e responsáveis por toda a mágica até agora são só e somente só Products – que “armazena” os produtos num formato de lista – e Product – que é a encarnação de um produto qualquer. Por fim, como que nos comunicaríamos com o cabeçalho (ou “header”, em inglês, se preferir) através da MyApp.Views.Products.List? Ora, quando um novo Product for adicionado à Products, o “Header” precisa saber incrementar o valor por ele comportado.
Introduzindo EventAggregators
Um Event Aggregator não é um mecanismo do Backbone ou exclusividade de alguma tecnologia específica, mas sim um serviço de intermediação entre “coisas”: ele ouve uma mensagem e a leva até algum lugar, onde, nesse lugar, o receptor fará algo baseado nessa mensagem. Em linhas curtas, você pode, por exemplo, usá-lo para intermediar eventos entre camadas. Mas, “camadas”? O que isso significa exatamente? Então, esse termo é conceitual, eu sei – para você entender melhor, me refiro à camadas como sendo classes, protótipos ou arquivos; entidades diferentes, por assim falar melhor. Na prática, List é o recipiente de Products – precisa explicar pro Header para somar 1 (um) ao valor da sua contagem de produtos (somente) quando um item for adicionado. Para isso, já vi soluções como essa:
/**
* A view "concreta" da lista de produtos – eis a
* encarnação da coleção.
*/
MyApp.Views.Products.List = new Backbone.View.extend({
/**
* Aqui você tem os atributos e/ou métodos
* da sua view que funcionam perfeitamente.
*/
collection: MyApp.Collections.Products,
add: function () {
var item = new MyApp.Models.Product({
id: 1,
name: 'Livro Backbone.js'
});
this.collection.add(item);
var counter = new MyApp.Views.Products.Header;
counter.refresh(this.collection.length);
});
/**
* A view "concreta" do cabeçalho, onde o contador
* fica "hospedado".
*/
MyApp.Views.Products.Header = new Backbone.View.extend({
/**
* Aqui você tem os atributos e/ou métodos
* da sua view que funcionam perfeitamente.
*/
refresh: function (quantity) {
this.$el.find('.counter').html(quantity);
}
});
E aí, o que você acha? Em primeiro lugar, se você copiar e colar este código em um JavaScript seu, não vai funcionar. Isso porque são códigos demonstrativos e estão incompletos, mas a demonstração é conceitual. Em segundo lugar, se o código estivesse completo e seguindo esse conceito, essa “técnica” funcionaria sim. Entretanto, está (muito) longe de estar correta e incentivada, porque não faz sentido algum você instanciar (veja o new) uma view para essa finalidade – na verdade, você pode instanciar quantas views você quiser, desde que seja para a sua real finalidade: renderização. Em terceiro lugar, (já) existe uma solução muitíssimo mais elegante para contornar essa situação. Aliás, usar a palavra “incorreto” é errado, porque soa como uma “gambiarra”. Veja:
/**
* No escopo global, você deve injetar ao Backbone
* o (seu) método personalizado de agregação de eventos,
* como o a seguir.
*
* Lembrando que o objeto "Backbone" precisa estar também
* disponível globalmente para que a injeção seja feita
* com êxito.
*/
Backbone.EventAggregator = _.extend({}, Backbone.Events);
Depois nós podemos finalmente fazer com que um evento exploda quando um item – ou produto, como preferir – for adicionado à sua lista de produtos:
/**
* A view "concreta" da lista de produtos – eis a
* encarnação da coleção.
*/
MyApp.Views.Products.List = new Backbone.View.extend({
/**
* No método a seguir, estamos à adicionar
* um item à lista.
*
* Exatamente nesse momento é que temos que incrementar
* o valor contido lá no cabeçalho – para isso,
* dessa vez, usaremos o EventAggregator.
*/
add: function () {
var item = new MyApp.Models.Product({
id: 1,
name: 'Livro Backbone.js'
});
this.collection.add(item);
/**
* O valor do primeiro parâmetro é o nome dado à ação
* engatilhada.
*
* Note que esse nome nada tem a ver com o método
* a ser executado quando este engatilhador for acionado.
*
* O valor do segundo parâmetro, por sua vez, é o dado
* que você quer transportar de, no nosso caso, uma
* view para a outra.
*/
Backbone.EventAggregator
.trigger('refreshCounter', this.collection.length);
}
});
/**
* A view "concreta" do cabeçalho, onde
* o contador fica "hospedado".
*/
MyApp.Views.Products.Header = new Backbone.View.extend({
/**
* Dessa vez, apresento-lhes o construtor: aqui, você
* invocará um "listener", este que será responsável
* por "fazer alguma coisa" quando "algo acontecer".
*/
initialize: function () {
/**
* O método em questão já não é mais o ".trigger()",
* mas sim o ".on()". Traduzindo arbitrariamente:
* "Agregador de eventos, quando 'refreshCounter'
* for acionado, execute 'this.refresh' enviando
* como parâmetro 'this'".
*
* Assim, após a execução de ".add()" da nossa
* view "List", executaremos o "refreshCounter" que,
* por sua vez, executará o método ".refresh()" da
* view "Header".
*
* Para constar, o "this" em ".refresh" significa
* que é um método
* do objeto instanciado. A ausência de parênteses,
* por outro lado, se traduz por uma trivial
* referenciação ao método.
*/
Backbone.EventAggregator
.on('refreshCounter', this.refresh, this);
},
/**
* O método mantém-se intacto, como você pode ver.
*/
refresh: function (quantity) {
this.$el.find('.counter').html(quantity);
}
});
Além de termos menos código utilizando o EventAggregator, estamos utilizando de uma boa prática que só vai somar na escalabilidade e manutenção do nosso software.
Quero salientar ainda que existem serviços de eventos prontos e relativamente mais avançados, como o já falado Backbone.Wreqr – já depreciado – e o Backbone.Radio; portanto, fazendo jus ao título, apresentei a forma mais pura possível, sem injetar qualquer terceirização ao seu Backbone e naturalmente ao seu software.
Se você quiser uma leitura mais aprofundada sobre o serviço EventAggregator, recomendo esta leitura, desenvolvida pela Microsoft, e esta outra, de 2004, por Martin Flower. Por hoje é isso! Espero que gostem.




