Backbone.js nas trincheiras
                Giovanni Bassi              Osmar Landin
     giovanni@lambda3.com.br osmar.landin@lambda3.com.br
               @giovannibassi               @osmarlandin
Agenda
                 Porque
   O que é                   Estruturando o
              BackboneJS e
 BackboneJS                      projeto
                cenários



  Usando o    Testando o
                              Conclusões
 BackboneJS   BackboneJS
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              Osmar Landin
giovanni@lambda3.com.br osmar.landin@lambda3.com.br
          @giovannibassi               @osmarlandin
Obrigado!
           Giovanni Bassi              Osmar Landin
giovanni@lambda3.com.br osmar.landin@lambda3.com.br
          @giovannibassi               @osmarlandin
www.lambda3.com.br

LambdaDay: Backbone.js

  • 1.
    Backbone.js nas trincheiras Giovanni Bassi Osmar Landin giovanni@lambda3.com.br osmar.landin@lambda3.com.br @giovannibassi @osmarlandin
  • 2.
    Agenda Porque O que é Estruturando o BackboneJS e BackboneJS projeto cenários Usando o Testando o Conclusões BackboneJS BackboneJS
  • 3.
    O que éBackbone.js
  • 4.
    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)
  • 5.
  • 6.
    BackboneJS x <algumserver> • 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
  • 7.
    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
  • 8.
  • 9.
    Separação da IGe 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
  • 10.
    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)
  • 11.
    Template <section id="eventosApp"> <header>Eventos</header> </section>
  • 12.
    Consumidor da view require[ 'views/AppView' ], (AppView) -> appView = new AppView el:$("#app-container") appView.render()
  • 13.
    Separação do códigoda app e das bibliotecas • Uso de RequireJS para modularização (usando AMD) • Separação das pastas da aplicação entre: – Bibliotecas – Aplicação – Testes
  • 14.
    Estrutura de diretórios •Aplicação – Scripts (bibliotecas) – App (código da aplicação) • Models • Views – Templates • Router – AppTestes • Models • Views
  • 15.
    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>
  • 16.
    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'
  • 17.
    RequireJS (módulo) define [ 'Backbone' 'Handlebars' ], (Backbone, Handlebars) ->
  • 18.
  • 19.
    Backbone.js não existesozinho • Dependência direta do Underscore (ou Lo-Dash) • Para manipulação da view utiliza jQuery (ou Zepto) – Recomendamos também o Handlebars para templates
  • 20.
    Roteadores (Backbone.Router) • Interceptaas 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
  • 21.
    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()
  • 22.
    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
  • 23.
    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'
  • 24.
    Template <tr> <td>{{Id}}</td> <td>{{Nome}}</td> <td>{{{formataData Data}}}</td> <td>{{QuantidadeVagas}}</td> </tr>
  • 25.
    Modelos (Backbone.Model) • Representamo 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
  • 26.
    Model define [ 'Backbone' ], (Backbone) -> class EventoModel extends Backbone.Model idAttribute: "Id" urlRoot:"api/eventos"
  • 27.
    Coleções (Backbone.Collection) • Representamuma 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
  • 28.
    Collection define [ 'Backbone' 'models/EventoModel' ], (Backbone, EventoModel) -> class BackboneCollection extends Backbone.Collection url: '/api/eventos' model: EventoModel
  • 29.
  • 30.
    Backbone é bastantetestá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
  • 31.
    Testando a renderizaçãoda 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')
  • 32.
    Mockando Ajax describe 'Aosalvar 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()
  • 33.
    Dúvidas? Giovanni Bassi Osmar Landin giovanni@lambda3.com.br osmar.landin@lambda3.com.br @giovannibassi @osmarlandin
  • 34.
    Obrigado! Giovanni Bassi Osmar Landin giovanni@lambda3.com.br osmar.landin@lambda3.com.br @giovannibassi @osmarlandin
  • 35.

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