Backbone.js nas trincheiras
Giovanni Bassi
giovanni@lambda3.com.br
@giovannibassi
Osmar Landin
osmar.landin@lambda3.com.br
@osmarlandin
Agenda
O que é
BackboneJS
Porque
BackboneJS e
cenários
Estruturando o
projeto
Usando o
BackboneJS
Testando o
BackboneJS
Conclusões
O que é Backbone.js
Backbone.js
• Componente Javascript
– Pode ser usado com CoffeeScript ou TypeScript
• MV* - Separação de responsabilidades entre modelo,
view, e roteador (mais ou menos um MVC)
• Um dos frameworks JS mais usados no mundo
• Bem documentado
• Open source (hospedado no github)
Porque Backbone.js?
Características do cenário
BackboneJS x <algum server>
• Prós
– Maior responsividade da aplicação
– Mais cara de aplicação, menos cara de site
– Código de interface concentrado mais perto do navegador
– Interfaces mais ricas
• Contras
– Ferramental ainda em evolução
– Curva de aprendizado
– Hoje ainda é mais lento para desenvolver
Cenário
• Maior responsividade da aplicação
• Testabilidade
• Documentação dos componentes
• Estabilidade dos componentes
• Rodar na internet e na intranet
• Navegadores modernos
• Milhares de usuários
Estruturando o projeto
Separação da IG e comportamento
• Uso do Backbone.js e Mustache.js, depois substituído
por Handlebars (uma extensão do Mustache)
• Backbone.js provê a ideia de views e templates,
facilitando a manipulação da view
View
define [
'Backbone'
'Handlebars'
'text!views/templates/AppViewTemplate.html'
],
(Backbone, Handlebars, Template) ->
class AppView extends Backbone.View
template: Template
render: ->
@$el.html Handlebars.compile(@template)
Template
<section id="eventosApp">
<header>Eventos</header>
</section>
Consumidor da view
require [
'views/AppView'
],
(AppView) ->
appView = new AppView
el:$("#app-container")
appView.render()
Separação do código da app e das bibliotecas
• Uso de RequireJS para modularização (usando AMD)
• Separação das pastas da aplicação entre:
– Bibliotecas
– Aplicação
– Testes
Estrutura de diretórios
• Aplicação
– Scripts (bibliotecas)
– App (código da aplicação)
• Models
• Views
– Templates
• Router
– AppTestes
• Models
• Views
RequireJS (html)
<!DOCTYPE html>
<html>
<head>
<script data-main="App/bootstrap.js" src="Scripts/require.js"></script>
</head>
<body>
<section id="app-container"></section>
</body>
</html>
RequireJS (Bootstrap)
require.config
paths:
jquery: '../Scripts/jquery-1.9.1'
jQueryUI: '../Scripts/jquery-ui-1.10.0'
Underscore: '../Scripts/underscore'
Backbone: '../Scripts/backbone'
Handlebars: '../Scripts/handlebars'
TwitterBootstrap: '../Scripts/bootstrap'
text: '../Scripts/text'
shim:
'jQueryUI':
deps: ['jquery']
'Handlebars':
deps: ['jquery']
exports: 'Handlebars'
RequireJS (módulo)
define [
'Backbone'
'Handlebars'
],
(Backbone, Handlebars) ->
Aprofundando no
Backbone.js
Backbone.js não existe sozinho
• Dependência direta do Underscore (ou Lo-Dash)
• Para manipulação da view utiliza jQuery (ou Zepto)
– Recomendamos também o Handlebars para templates
Roteadores (Backbone.Router)
• Intercepta as urls e encaminha para um método
• Cuida do histórico (back, forward, etc)
• Geralmente só há um roteador na app (grande
potencial para problemas se você ignorar essa regra)
• Pode ficar bem grande
• Geralmente passa o controle para alguma view
http://localhost/#novo
define ['jquery‘, 'Backbone‘, 'views/AppView‘,
'views/ListaEventosView‘,'views/CadastroEventoView'],
($, Backbone, AppView, ListaEventosView, CadastroEventoView) ->
class router extends Backbone.Router
routes:
'':'home'
'novo':'criarEvento'
initialize: ->
appView = new AppView
el:$("#app-container")
appView.render()
Backbone.history.start()
home: ->
listaEventosView = new ListaEventosView { el:$("#app-content") }
listaEventosView.render()
criarEvento: ->
cadastroView = new CadastroEventoView
el:$("#app-content")
cadastroView.render()
Views
• Não é bem uma view, é mais ou menos um controller
• Em geral busca os dados em algum model ou
collection, e renderiza o html (com um template ou
não)
• Intercepta os eventos do html e se comunica com o
model ou collection, que por sua vez, falam com o
servidor
View
define ['jquery','Backbone','Handlebars','models/EventosCollection',
'views/ListaEventosItemView','text!views/templates/ListaEventosViewTemplate.html'],
($, Backbone, Handlebars, EventosCollection, ListaEventosItemView, Template) ->
class ListaEventosView extends Backbone.View
template: Template
events: -> 'click #incluir-evento':'criarEvento'
initialize: (options) ->
@el = options.el
@collection = new EventosCollection()
render: ->
@$el.empty()
@$el.html Handlebars.compile @template
@collection.fetch success: => @renderizarEventos()
renderizarEventos: ->
@collection.each (item) =>
itemView = new ListaEventosItemView {el:$("#listaEventos tbody"), model:item}
itemView.render()
criarEvento: -> window.location ='#novo'
Template
<tr>
<td>{{Id}}</td>
<td>{{Nome}}</td>
<td>{{{formataData Data}}}</td>
<td>{{QuantidadeVagas}}</td>
</tr>
Modelos (Backbone.Model)
• Representam o modelo de negócio, e também os
dados a serem exibidos/editados
• Tem conexão direta com o servidor, um modelo sabe
se recuperar no server
• Representam uma única entidade
Model
define [
'Backbone'
],
(Backbone) ->
class EventoModel extends Backbone.Model
idAttribute: "Id"
urlRoot:"api/eventos"
Coleções (Backbone.Collection)
• Representam uma coleção de entidades do modelo
• Permitem obter diversas entidades de uma única vez,
com ou sem filtros na consulta
• Permite criar, excluir, atualizar entidades
Collection
define [
'Backbone'
'models/EventoModel'
],
(Backbone, EventoModel) ->
class BackboneCollection extends Backbone.Collection
url: '/api/eventos'
model: EventoModel
Testando
Backbone é bastante testável
• Testes de unidade com diversos frameworks possíveis,
como QUnit, Jasmine e outros (usamos Jasmine com
Jasmine-JQuery)
• Os testes não batem no server, não há necessidade de
rodar o lado do servidor para os testes passarem
• Faça testes de unidade!
• Faça também testes de integração dirigindo o browser
Testando a renderização da view
define ['views/ListaEventosItemView'], (ListaEventosItemView) ->
element = $("<div></div>")
subject = null
model = new Backbone.Model()
model.set
"Id":7
"Nome":"Evento 1"
"Data":"2013-03-14T12:56:59.0934901-03:00"
"QuantidadeVagas":100
describe 'Lista Eventos Item View', ->
beforeEach ->
subject = new ListaEventosItemView { el:element, model:model }
describe 'Ao renderizar', ->
beforeEach ->
subject.render()
it 'deve exibir o id do evento', ->
expect(subject.$el.html()).toContain('7')
it 'deve exibir o nome do evento', ->
expect(subject.$el.html()).toContain('Evento 1')
it 'deve exibir a data do evento já formatada', ->
expect(subject.$el.html()).toContain('3/14/2013')
Mockando Ajax
describe 'Ao salvar o modelo com sucesso', ->
beforeEach ->
spyOn($, "ajax").andCallFake (parametros) ->
parametros.success
Id:1
Nome:"Evento 1"
Data:"2013-03-14T12:56:59.0934901-03:00"
QuantidadeVagas:100
it 'deve redirecionar para a listagem de eventos', ->
$("#salvar", subject.el).click()
expect(subject.exibirLista).toHaveBeenCalled()
Dúvidas?
Giovanni Bassi
giovanni@lambda3.com.br
@giovannibassi
Osmar Landin
osmar.landin@lambda3.com.br
@osmarlandin
Obrigado!
Giovanni Bassi
giovanni@lambda3.com.br
@giovannibassi
Osmar Landin
osmar.landin@lambda3.com.br
@osmarlandin
www.lambda3.com.br

Backbone.js nas trincheiras

Notas do Editor

  • #3 $el = Um objeto Jquery (ou Zepto) cacheado para o elemento da view. Uma referência à mão para não precisar pesquisar o elemento no DOM toda hora
  • #4 AMD - Asynchronous Module DefinitionEspecifica um mecanismo para definição de módulos, de forma que esses módulos e suas referências possam ser carregadas assincronamente.O AMD veio do desejo de ter um formato que fosse melhor que “escrever um monte de tags script com dependências implícitas onde você precisa manualmente pedir”.
  • #5 O atributo data-main é um atributo especial que o RequireJS checará para iniciar o carregamento dos scripts.
  • #6 O jQuery não está especificado no shim porque o jQuery suporta AMD (existe a definição do módulo no arquivo jQury.js)
  • #7 Jasmine-Jquery = é uma extensão do Jasmine (facilita osasserts e a manioulação do HTML, CSS
  • #8 Um SPY faz um mock de qualquer função e rastreia as chamadas a elas e todos os parâmetros/argumentos enviados