Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Web Development
Code

Um Aplicativo Lista de Tarefas de Página Única Usando Backbone.js

by
Difficulty:BeginnerLength:LongLanguages:

Portuguese (Português) translation by Erick Patrick (you can also view the original English article)

Backbone.js é uma framework JavaScript para a criação de aplicações web flexíveis. Ela vem com Modelos, Coleções, Visões, Eventos, Roteador e algumas outras ótimas funcionalidades. Nesse artigo, desenvolveremos um simples aplicativo de lista de tarefas que dará suporte a adicionar, editar e remover tarefas. Também seremos capazes de marcá-las como realizadas ou arquivá-las. Para manter esse post em um tamanho aceitável, não incluiremos nenhuma capacidade de comunicação com base de dados. Todos os dados serão mantidos no lado do cliente.

Preparação

Essa será a estrutura de arquivos/diretórios que usaremos:

Algumas coisas são bem óbvias, como o /css/styles.css e /index.html. Elas contem os estilos CSS e o código HTML da página inicial, respectivamente. No contexto do Backbone.js, o modelo é um lugar onde guardamos nossos dados. E, como teremos mais de uma tarefa, organizaremos esses modelos em uma coleção. A lógica de negócios está distribuida entre as visões e o arquivo principal da aplicação, o App.js. O Backbone.js só possui uma única dependência - o Underscore.js. O framework funciona bem com o jQuery, assim, ambos vão para o diretório vendor. Tudo que precisamos, agora, é um pouco de código HTML já preparado e estaremos prontos.

Como pode ver, incluímos todos os arquivos JavaScripts externos no final da página, logo antes da tag body, uma vez que isso é uma boa prática de desenvolvimento. Também preparamos a base da aplicação. Há um recipiente para o conteúdo, um menu e um título. A navegação principal é um elemento estático e não a mudaremos. Por outro lado, mudaremos o conteúdo do título e da div logo abaixo.

Planejando a Aplicação

Sempre é bom ter algum plano antes de começarmos a trabalho em algo. O Backbone.js não possui uma arquitetura super estrita, a qual devemos seguir. Esse é um de seus benefícios. Assim, antes de começarmos a implementar a lógica de negócios, falemos sobre o básico.

Espaço de Nomes

É uma boa prática criar um escopo próprio para seu código. Registrar variáveis ou funções globais não é uma boa ideia. Nós criaremos um modelo, uma coleção, um roteador e algumas visões do Backbone.js. Todos esses elementos devem ter um espaço próprio, privado. O arquivo App.js conterá a classe que agrupa tudo.

O que temos acima é uma implementação típica do padrão do módulo revelador. A variável api é o objeto que será retornado e representa os métodos públicos da classe. As propriedades views, models e collections agirão como recipientes para as classes retornadas pelo Backbone.js. A propriedade content é um objeto jQuery apontando para o recipiente principal da interface do usuário. Temos dois métodos auxiliares, também. O primeiro atualiza o recipiente. O segundo atribui um título à página. E então, definimos um módulo chamado ViewsFactory. Ele entregará nossas visões e, por fim, criamos nossa rota.

Talvez esteja perguntando o porque de precisarmos de uma fábrica para nossas visões, certo? Bem, existem alguns padrões comuns ao se trabalhar com o Backbone.js. Um deles é em relação à criação e uso de visões.

É bom inicializar as visões somente uma vez e deixá-las existir durante o tempo de execução da aplicação. Uma vez que os dados mudem, geralmente invocamos métodos das visões e atualizamos o conteúdo do respectivo objeto el. Uma outra abordagem bastante popular é a de recriar toda a visão ou substituir todo o elemento DOM. Contudo, não é uma opção muito boa em relação a performance. Assim, nós temos uma classe auxiliar que cria uma instância da visão e a retorna quando precisarmos dela.

Definição de Componentes

Temos um espaço de nomes (namespace), então já podemos começar a criar componentes. É assim que o menu principal será:

Criamos uma propriedade chamada menu que guardará a classe da navegação. Mais para frente, podemos adicionar algum método ao módulo da fábrica, capaz de criar uma instância dela própria.

O código acima é o responsável pela manipulação das visões e garantirá que tenhamos apenas uma única instância. Essa técnica funciona bem, na maioria dos casos.

Fluxo

A porta de entrada da aplicação é o arquivo App.js e seu método init. Será esse método que invocaremos no manipulador onload do objeto window.

Depois disso, o roteador que definimos tomará controle. Baseado na URL, ele decidirá qual manipulador executar. No Backbone.js, não temos uma arquitetura MVC (Model-View-Controller) padrão. Falta o controlador e a maior parte da lógica fica dentro das visões. Assim, atrelamos os modelos diretamente aos métodos, dentro das visões, e obtemos uma atualização instantânea da interface do usuário, assim que os dados mudarem.

Administrando os Dados

O mais importante em nosso pequeno projeto são os dados. Nossas tarefas são aquilo que devemos administrar, então, comecemos a partir daí. Eis nossa definição de modelo.

Apenas três campos. O primeiro contem o texto da tarefa e os outros dois são marcações que definem o estado do registro.

Tudo dentro da framework, na verdade, é um emissor de eventos. E, uma vez que o modelo é modificado através de setters, a framework sabe quando o dado foi atualizado e pode notificar o resto do sistema. Assim que você atrela algo a essas notificações, sua aplicação reagirá às mudanças no modelo. Essa é uma funcionalidade muito poderosa do Backbone.js, de verdade.

Como disse no começo, teremos vários registros e organizaremos eles em uma coleção chamada ToDos.

O método initialize é a porta de entrada da nossa coleção. Em nosso caso, adicionamos algumas tarefas padrões. Claro, em uma aplicação de verdade, essas informações virão de uma base de dados ou de algum outro lugar. Mas, para continuarmos focados, faremos dessa forma. Uma outra coisa típica de coleções é a atribuição da propriedade model. Ela diz à classe qual o tipo de dados será salvo. O resto dos métodos implementam uma lógica própria do domínio de nossa aplicação. As funções up e down alteram a ordem dos itens da lista de tarefas. Para simplificar, identificaremos cada item com um simples índice no vetor da coleção. Isso significa que, se quisermos buscar algum registro em específico, devemos buscá-lo através de seu índice. Assim, a ordenação é só uma mudança de posição de elementos em um vetor. Como talvez tenha percebido no código acima, this.models é o vetor do qual estamos falando. Os métodos archive e changeStatus alteram as propriedades de um determinado item. Colocamos esses métodos aqui porque as visões terão acesso à coleção ToDos, não à tarefa diretamente.

Adicionalmente, não criaremos qualquer modelo a partir da classe app.models.ToDo, mas precisaremos criar uma instância da coleção app.collections.ToDos.

Mostrando Nossa Primeira Visão (Navegação Principal)

A primeira coisa que teremos de mostrar é a navegação principal da aplicação.

São somente nove linhas de código, mas muitas coisas interessantes estão acontecendo ali. A primeira, é que estamos configurando um modelo. Você lembra que adicionamos a Underscore.js ao nosso app? Usaremos o mecanismo de modelos dele, uma vez que funciona muito bem e é simples o bastante de se usar.

O que temos, no fim das contas, é uma função que aceita um objeto com os pares de chave-valor das informações e o código HTML do modelo na variável templateString. Certo, então ela aceita uma cadeia de caracteres HTML, mas o que aquele $("#tpl-menu").html() está fazendo ali? Quando desenvolvemos pequenos aplicativos de página única, geralmente colocamos os modelos diretamente na página, dessa forma:

E, uma vez que o modelo é uma tag script, não será mostrado ao usuário. Pensando de outra forma, ele é um elemento DOM válido e podemos obter seu conteúdo utilizando jQuery. Assim, aquele pequeno trecho de código ($("#tpl-menu").html()), é o encarregado de buscar o conteúdo da tag script.

O método render é bem importante para o Backbone.js. Essa é a função responsável por mostrar os dados. Geralmente, você atrela os eventos disparados pelo modelo, diretamente, nesse método. Contudo, para o menu principal, não precisamos de tal comportamento.

this.$el é um objeto criado pela framework e toda visão o tem por padrão (há um $ na frente do el porque incluimos a jQuery). E, por padrão, esse objeto representa uma <div></div> vazia. Claro, você pode alterar isso, usando a propriedade tagName. Mas, o mais importante, aqui, é que não atribuamos diretamente um valor ao objeto. Não o alteraremos diretamente, alteraremos seu conteúdo. Há uma grande diferença entre a linha acima e a linha abaixo:

O ponto é que, se você quiser ver as mudanças no navegador, você deverá chamar o método de redenrização (render) antes, para adicionar a visão à DOM. Caso contrário, somente a div vazia será adicionada. Tem outro cenário onde temos visões aninhadas. Uma vez que você modifique a propriedade, diretamente, o componente pai não é atualizado. Os eventos atribuídos talvez também não estejam funcionando e será preciso que atrele os observadores novamente. Por isso, você só deveria alterar o conteúdo de this.$el, não o valor da propriedade em si.

A visão está pronta e precisamos inicializá-la. Adicionemos nosso módulo fábrica:

No fim, apenas invoque o método menu na parte de preparação da aplicação (método init()):

Perceba que, ao criamos uma nova instância da classe de navegação, passamos um elemento DOM pré-existente, o $("#menu"). Assim, a propriedade this.$el dentro da visão estará apontando, na verdade, para $("#menu").

Adicionando Rotas

O Backbone.js dá suporte a operações push state. Em outras palavras, você pode manipular a URL atual do navegador e navegar entre as páginas a partir do histórico do navegador. Entretanto, continuaremos com as boas e velhas URLs de hashes, como /#edit/3.

Logo acima está nosso roteador. Há cinco rotas definidas em um objeto hash. A chave do objeto será o endereço que você digitará no navegador e o valor será a função que será chamada. Perceba que há o termo :index em duas das rotas. Essa é a sintaxe que você precisa usar se quiser que sua aplicação suporte URLs dinâmicas. Em nosso caso, se você digitar #edit/3 o método editToDo será executado com o parâmetro index=3. A última linha é um cadeia de caracteres vazia, indicando que a função list manipulará a página inicial da nossa aplicação.

Mostrando uma Lista com Todas as Tarefas

Até agora, criamos a visão principal para nosso projeto. Ele retornará os dados a partir da nossa coleção e mostrará esses dados na tela. Poderíamos usar essa mesma visão para duas coisas - mostrar todas as tarefas ativas, bem como mostrar aquelas que foram arquivadas.

Antes de continuarmos a implementação da lista, vejamos como ela é inicializada.

Perceba que passamos uma coleção. Isso é importante porque, mais para frente, usaremos this.model para acessar os dados guardados. A fábrica retorna nossa visão de lista, mas o roteador é o responsável por adicioná-la à página.

Por hora, o método list em nosso roteador é chamado sem qualquer parâmetro. Assim, a visão não estárá no modo archive (que mostrará as tarefas arquivadas), mostrando apenas as tarefas ativas.

A propriedade mode será usada durante a renderização. Se seu valor for mode="archive", então só as tarefas arquivadas serão mostradas. events é um objeto que o preencheremos já-já. É o local onde mapearemos os eventos da DOM. O resto dos métodos são respostas para as interações dos usuário e eles estão diretamente ligados às funcionalidades necessárias. Por exemplo, priorityUp e priorityDown muda a ordem da tarefas dentro da lista. archive move o item para a área de itens arquivados. changeStatus, simplesmente marca a tarefa como realizada.

O que está acontecendo dentro do método initialize é interessante. Antes, dissemos que você, normalmente, atrelaria as mudanças no modelo (a coleção, no nosso caso) ao método render da visão. Você pode digitar this.model.bind('change', this.render). Mas, logo, perceberá que a palavra-chave this no método render não corresponde à visão em si. Isso se dá porque o escopo é diferente. Como uma alternativa, criaremos um manipulador com um escopo, previamente, definido. É para isso que serve a função bind da Underscore.js.

Eis a implementação do método render.

Iteramos sobre todos os modelos da coleção e geramos um HTML que, posteriormente, será inserido no elemento DOM da respectiva visão. Há algumas checagens que distinguem a lista de tarefas padrão da lista de tarefas arquivadas. A tarefa é marcada como done com a ajuda de um campo próprio para isso. Assim, será possível indicar se precisamos passar o atributo checked="checked" para o elemento. Talvez tenha percebido que estamos usando o método this.delegateEvents(). No nosso caso, isso é necessário, porque estamos removendo e readicionando a visão da DOM. Exato! Não estamos substituindo o elemento principal, mas os manipuladores dos eventos são removidos. É por isso que temos de dizer ao Backbone.js para atrelá-los novamente. O modelo usado com o código acima é o que segue:

Note que tem uma classe CSS chamada done-yes, que pinta o plano de fundo da tarefa na cor verde. Além disso, há algumas âncoras que usaremos para implementar as funcionalidades necessárias. Elas todas possuem atributos de dados (atributos que começam com data-). O elemento do nodo principal, li, tem o atributo de dados data-index. O valor desse atributo é responsável por apresentar o índice da tarefa dentro da coleção. Note que as expressões especiais envolvidas em <%= ... %> são enviadas para a função template. Serão nessas expressões que os dados serão injetados no modelo.

É hora de adicionar alguns eventos à visão.

No Backbone.js a definição de eventos é um simples hash. Você, primeiramente, digita o nome do evento e, então, um seletor. Os valores das propriedades são, na verdade, métodos das visões.

Aqui, usamos o e.target que vem do manipulador. Ele aponta para o elemento Dom que ativou o evento. Obtemos o índice da tarefa que foi clicada e atualizamos o modelo na coleção. Com essas quatro funções, finalizamos nossa classe e agora temos dados para mostrarmos na página.

Como mencionado acima, usaremos a mesma visão para a página Arquivos.

Acima temos o mesmo manipulador que antes, porém, dessa vez, passamos um parâmetro true.

Adicionando & Editando Tarefas

Seguindo os princípios da visão da lista, poderíamos criar outra que mostra um formulário para adicionar e editar tarefas. Eis como essa novo classe pode ser criada:

Praticamente a mesma coisa de antes. Entretanto, agora, precisamos realizar uma operação assim que o formulário for submetido. E, depois disso, retornar o usuário para a página inicial. Como já disse, todo objeto que estende alguma classe do Backbone.js é, na verdade, um emissor de eventos. Existem métodos como o on e o trigger, os quais podem ser usados.

Antes de continuarmos com o código da visão, olhemos o modelo HTML:

Temos uma elemento textarea e um button. O modelo espera por um parâmetro title, o qual deve ser uma cadeia de caracteres vazia, caso queiramos adicionar uma nova tarefa.

A visão só possui 40 linhas de código, mas é capaz de realizar a tarefa sem problemas. Existe só um evento atrelado que é o do clique do botão. O método render agirá diferente, dependendo do parâmetro index passado. Por exemplo, se formos editar alguma tarefa, passamos o índice e buscamos o modelo correspondente àquele índice. Caso contrário, o formulário estará vazio e uma nova tarefa será criada. Há várias pontos interessantes nesse trecho de códico. Primeiro, durante a renderização, usamos o método .focus() para focar no formulário, logo que a visão terminar de ser renderizada. E, mais uma vez, a função delegateEvents deve ser invocada, porque o formulário será removido e inserido novamente. O método save começa com e.preventDefault(). Isso remove o comportamento padrão do botão que, em alguns casos, pode submeter o formulário. E, ao final, uma vez que tudo está pronto, ativamos o evento saved, notificando o 'mundo exterior' que a tarefa foi salva na coleção.

Há outros dois métodos do roteador, que devemos finalizar.

A diferença entre os dois é que passamos um índice, caso a URL combine com o padrão da rota edit/:index. E, claro, o título da página muda de acordo, também.

Removendo um Registro da Coleção

Para essa funcionalidade, não precisamos de uma visão. Todo o trabalho pode ser realizado no manipulador da rota.

Sabemos o índice da tarefa que queremos remover. Há o método remove na classe da coleção, que aceita um objeto do modelo, como parâmetro. Depois de removido, basta redirecionar o usuário para a página inicial, que mostrará a lista atualizada.

Conclusão

O Backbone.js tem tudo que você precisa para construir uma aplicação de página única, totalmente funcional. Poderíamos até usá-la com um serviço REST e ela seria responsável pode sincronizar os dados entre seu aplicativo e a base de dados. A abordagem voltada a eventos encoraja uma programação modular, bem como uma boa arquitetura. Uso o Backbone.js para vários projetos e ele funciona muito bem.

Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.