O Ember.js é uma sólida estrutura de aplicativo de página única para a criação de aplicativos modernos da Web.

Antes de Angular e Reagir atingiu a massa crítica, Ember.js estava liderando a criação de aplicativos escalonáveis de página única. Embora os holofotes possam ter mudado, o Ember.js continua sendo uma opção excelente e viável para os desenvolvedores que desejam criar protótipos e clientes front-end modernos.


O Ember é uma ótima alternativa ao Angular 2 devido à sua maturidade. A CLI do Ember foi até mesmo usada como inspiração para a criação da CLI do Angular 2 para ajudar os desenvolvedores a estruturar seus aplicativos de forma mais eficaz. Em comparação com o React, o Ember oferece um pouco mais de recursos prontos para uso, como roteamento e uma estrutura de modelo bem definida.


No tutorial de hoje, vamos criar um aplicativo de página única com o Ember.js 2.x. O Ember.js 2.x, assim como o Angular 2, é mais uma estrutura e apresenta uma melhoria de desempenho de 10 vezes em relação à iteração 1.x. Para o nosso backend, em vez de criar e implantar um servidor Web tradicional, criaremos um Webtask que fornecerá a funcionalidade do lado do servidor. Com o cenário pronto, vamos começar.



Criando o back-end com o Webtask


O aplicativo que criaremos com o Ember.js será um aplicativo de crowdsourcing para eventos. Os administradores do aplicativo criarão eventos nos quais os usuários da plataforma poderão votar. Cada evento terá um determinado requisito de votos necessários para que possa ser realizado. Todos poderão visualizar os eventos, mas somente os usuários autenticados poderão votar nos eventos. Começaremos criando nosso Webtask backend alimentado.


Webtask é uma plataforma sem servidor, de função como serviço, desenvolvida pela Auth0 que permite que os desenvolvedores criem microsserviços e os exponham via HTTP. Se ainda não tiver uma conta Webtask, o senhor pode se inscrever para obter uma gratuitamente aqui. Depois de se inscrever, o senhor precisará instalar o CLI do Webtask em seu computador, executando npm install wt-cli -g. Node.js e NPM são os únicos pré-requisitos necessários.


Depois de instalar o Webtask CLI, execute o comando wt-cli init e o senhor será solicitado a fornecer um e-mail ou número de telefone. Forneça um deles e o senhor receberá um código de confirmação que deverá ser inserido na CLI para concluir o processo de autenticação. Feito isso, o senhor estará pronto para escrever e implementar Webtasks.



Há muitos abordagens diferentes para escrever Webtasks. Podemos escrever uma Webtask que executa uma única função, como enviar um e-mail de boas-vindas quando um usuário se registra, ou podemos escrever um aplicativo inteiro dentro da Webtask. Optaremos pela segunda opção e criaremos toda a nossa implementação de backend com uma única Webtask. Queremos nos concentrar no Ember.js, portanto, examinaremos rapidamente essa seção. O senhor sempre pode saber mais sobre o funcionamento do Webtask lendo o docs.



Nossa Webtask terá quatro rotas. A /events retornará uma lista de todos os eventos disponíveis, a rota /events/:id retornará um único evento, a rota /events/:id PUT incrementará a contagem de votos e, por fim, a rota ‘/seed’ semeará nosso banco de dados (armazenamento do Webtask) com alguns eventos iniciais para votação.



Dê uma olhada em nossa implementação de backend no código abaixo. A Webtask que estamos criando será muito semelhante a uma Express.js com algum código específico do Webtask. Se algum dos conceitos não fizer sentido ou se o senhor desejar alguns recursos adicionais, consulte a seção Documentos do Webtask, especificamente sobre armazenamento e autenticação para saber mais. Armazenaremos esse código em um arquivo chamado api.js.




// Get our dependencies
var app = new (require('express'))();
var wt = require('webtask-tools');

// Define the events route which will retrieve a list of all events
app.get('/events', function(req, res){
    req.webtaskContext.storage.get(function (error, data) {
        if (error) return res.send(error);
        res.json({event: data});
    });
})

// Return a specific event based on the event ID attribute
app.get('/events/:id', function(req,res){
  req.webtaskContext.storage.get(function(error, data){
    if(error) return res.send(error);
    for(var i = 0; i < data.length; i++){
      if(req.params.id == data[i].id){
        res.json({event : data[i]})
      }
    }
  })
})

// The PUT request to the events/:id route will increment the vote count of the particular event
app.put('/events/:id', function(req, res){
  req.webtaskContext.storage.get(function(error, data){
    for(var i = 0; i < data.length; i++){
      if(req.params.id == data[i].id){
        data[i].votes += 1;
        req.webtaskContext.storage.set(data, function(err){
          res.json({status: 'ok'})
        })
      }
    }
  })
})

// Once our Webtask is live, we'll hit this route once, to seed our event database
app.get('/seed', function(req, res){
  req.webtaskContext.storage.get(function (error, data) {
    if (error) return cb(error);
      data = events();
      req.webtaskContext.storage.set(data, function (error) {
        if (error) return cb(error);
        res.json({status:'ok'});
      });
  });
})

module.exports = wt.fromExpress(app)

// This function will return our seed data.
function events(){
  return [
    {
      id: 10432,
      name : "Meet and Greet: Kobe Bryant",
      description: "See the legendary Kobe Bryant talk about his career with the NBA and how he became one of the greatest players of all time.",
      img : "",
      votes: 10289,
      required: 25000,
      featured: true
    },
    {
      id: 14582,
      name : "Marvel vs. DC at San Diego Comic Con",
      description: "Watch the battle between the greatest superheros at Comic Con 2017.",
      img : "",
      votes: 14900,
      required: 20000,
      featured: false
    },
    {
      id: 42000,
      name : "AMA: Elon Musk",
      description: "Ask Elon Musk anything. The CEO of Tesla and Space X has agreed to answer any and all of your questions.",
      img : "",
      votes: 10289,
      required: 10000,
      featured: false
    },
    {
      id: 54200,
      name : "Secret Event",
      description: "This could be anything. Vote to find out!!!",
      img : "",
      votes: 4754,
      required: 7500,
      featured: false
    },
    {
      id: 55900,
      name : "Meet the Developers",
      description: "Meet the developers building this awesome platform.",
      img : "",
      votes: 5900,
      required: 5000,
      featured: false
    },
  ]
}



Com a implementação em vigor, estamos prontos para implantar nosso backend. Execute o wt-cli create api.js no diretório em que o seu recém-criado api.js e, em poucos segundos, sua Webtask será criada e implementada. Acesse o URL fornecido na CLI e você verá seu código em execução. O primeiro endpoint que o senhor deve acessar deve ser o /seed pois ele alimentará o armazenamento do Webtask com alguns eventos. Até aqui tudo bem. Em seguida, vamos criar nosso front-end.

Criando nosso SPA com Ember.js


O Ember.js foi pioneiro no uso de um interface de linha de comando (CLI) para facilitar a criação de andaimes e ajudar no desenvolvimento de aplicativos da Web. Ao criar nosso aplicativo, usaremos a CLI. Para instalar a CLI, execute o comando npm install ember-cli -g . Novamente, o Node.js e o NPM são pré-requisitos para obter a CLI. Depois que a CLI estiver instalada, o senhor estará pronto para começar a criar seu aplicativo.



Para criar um novo aplicativo Ember.js, execute ember new e um nome para o aplicativo. Vamos executar ember new events-app. O Ember criará automaticamente um novo diretório intitulado events-app, crie uma estrutura básica de aplicativo e obtenha todas as dependências necessárias. Isso pode levar alguns minutos para ser concluído. Quando a instalação estiver concluída, navegue até o diretório events-app digitando cd events-app e pressionando a tecla Return no teclado.



Para garantir que nosso aplicativo foi inicializado corretamente, vamos executar o comando ember server e navegar até localhost:4200 em seu navegador. Se o senhor vir uma mensagem dizendo “Congratulations you made it!” (Parabéns, você conseguiu!), então está tudo certo. Caso contrário, sugiro que o senhor execute novamente o ember new como NPM e Bower pode, às vezes, não conseguir baixar todas as dependências corretamente.




A estruturação de um aplicativo dessa maneira nos dá um ponto de partida muito básico, ótimo para criar aplicativos do zero. Temos uma meta ambiciosa neste tutorial e muito a cobrir, portanto, usaremos um projeto inicial diferente para continuar nosso desenvolvimento. Usaremos o projeto Início rápido do Auth0 Ember.js pois vamos adicionar autenticação de usuário ao nosso aplicativo. Muitos dos tópicos que abordaremos serão exatamente os mesmos que o senhor abordaria se continuasse a partir do scaffold que criamos anteriormente, mas isso nos permitirá avançar um pouco mais rápido.



Acesse o site do Início rápido do Auth0 Ember.js 2 e faça o download do projeto inicial fornecido. Será necessário inscrever-se em uma conta Auth0 para implementar a funcionalidade de usuário autenticado, portanto, se ainda não tiver uma conta, inscreva-se em uma aqui. Depois de fazer o download do início rápido, abra o diretório e execute npm install seguido de bower install. Isso fará o download e instalará todas as dependências de que precisaremos. Com as dependências instaladas, execute ember server e navegue até localhost:4200 para ver o aplicativo de início rápido padrão.



Não há muito o que fazer até agora. Vamos primeiro definir algumas das configurações ambientais e, em seguida, começaremos a criar o aplicativo. Abra o arquivo environement.js localizado no diretório config. Nesse arquivo, navegue até o atributo intitulado Auth0 e altere o atributo clientID e domain para seu Auth0 ClientID e domínio. O senhor pode encontrar esses valores em seu Auth0 painel de gerenciamento.



Se o seu servidor Ember ainda estiver em execução, o senhor perceberá que todas as alterações que fizer serão refletidas em tempo real. Esse é outro benefício de criar o aplicativo com a CLI. O ember server executa o live-sync e observa o aplicativo; sempre que uma alteração é feita, o servidor é reiniciado automaticamente.



Agora, temos uma estrutura muito boa do nosso aplicativo. Em seguida, vamos adicionar a biblioteca CSS do Bootstrap para que possamos estilizar facilmente nosso aplicativo. Abra a biblioteca index.html e, na seção head, adicione a tag Bootstrap 3 a partir de uma CDN. Obteremos o Bootstrap do MaxCDN e adicionaremos o seguinte em nosso index.html : <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" > . Se o senhor olhar para o seu aplicativo agora, perceberá que ele está muito melhor.



Vamos abrir o arquivo application.hbs a seguir. Edite o conteúdo do arquivo application.hbs da seguinte forma:



{{outlet}}


Por enquanto, faremos com que o arquivo contenha apenas a única linha {{outlet}}. Se o senhor já trabalhou com estruturas SPA anteriores, talvez já saiba para que serve isso, mas, se não, é aqui que exibiremos os componentes com base no nosso roteador. Vamos criar uma rota para ver como essa funcionalidade funciona. A rota {{main-navigation}} que removemos exibia nossa navegação superior. Voltaremos a ele mais tarde.



Em sua janela de terminal, digite o seguinte comando ember generate route events e pressione Enter. Esse comando criará alguns arquivos diferentes para nós. O primeiro será um arquivo events.js no qual podemos adicionar nossa lógica de front-end para a rota de eventos. Em seguida, um arquivo events.hbs para o nosso modelo e, por fim, a CLI do Ember se encarregou de adicionar a rota de eventos ao nosso routes.js .



Por enquanto, basta abrir o arquivo events.hbs e adicionar um título à nossa página. Adicione o seguinte código: <h1>Events</h1>. Salve o arquivo e navegue até localhost:4200/events. O senhor verá o título exibido. Até agora, tudo bem.



O próximo conceito que eu gostaria de apresentar aos senhores é componentes. Os componentes do Ember.js nos permitem criar trechos reutilizáveis de funcionalidade. Adicionaremos um componente que exibirá os detalhes de cada um de nossos eventos. Para criar um componente, execute ember generate component app-event. Cada componente que o senhor criar precisará ter um hífen. A razão para isso é a compatibilidade. Se o senhor criar um componente chamado event e usá-lo como <event></event> em seu aplicativo e, em algum momento no futuro, o W3C decidir implementar uma tag <event>, seu aplicativo provavelmente será interrompido. O componente que criamos será implementado um pouco mais tarde; por enquanto, vamos colocar o restante de nossas rotas em um andaime.



Já temos a rota de eventos que exibirá uma lista de todos os nossos eventos. Em seguida, vamos criar uma rota para exibir apenas um único evento. Executar ember generate route event. Quando isso for feito, abra o arquivo router.js localizado no diretório app . Esse arquivo contém os dados do nosso roteador. Queremos fazer duas coisas aqui. Primeiro, definiremos uma rota padrão, que será nossa rota de eventos; em seguida, editaremos nossa rota de eventos para aceitar um parâmetro de rota. Dê uma olhada na implementação abaixo:




Router.map(function() {
  // Existing Routes added by the Auth0 Quickstart
  // We'll have a template for the existing index from the quick start.
  this.route(‘index');
  // Sets the default route to events
  this.route('events', { path: '/' });
  this.route('events')
  // Changes the default /event route to /event/:id where the :id is a variable
  this.route('event', {path: '/event/:id'});
});



Já tínhamos algumas rotas do Auth0 Ember.js Quickstart, portanto, vamos deixá-las como estão por enquanto, apenas faremos algumas edições em nossas rotas. Agora que temos nossas rotas definidas, vamos criar nosso aplicativo.



Vamos começar com a raiz do nosso aplicativo. Abra o diretório main-navigation.hbs localizado em templates/components. Substitua o código do modelo existente por:




<nav class="navbar navbar-default navbar-fixed-top">
  <div class="container-fluid">
    <div class="navbar-header">
      {{#link-to 'index' classNames="navbar-brand"}}
        Home
      {{/link-to}}
    </div>
    <ul class="nav navbar-nav navbar-left">
      {{#link-to 'events' tagName="li"}}
        <a>Events</a>
      {{/link-to}}
    </ul>
    {{! display logout button when the session is authenticated, login button otherwise }}
    {{#if session.isAuthenticated}}
      <a {{action 'logout'}} class="btn btn-danger navbar-btn navbar-right">Logout</a>
    {{else}}
      <a href="https://davidwalsh.name/login" class="btn btn-success navbar-btn navbar-right">Login</a>
    {{/if}}
  </div>
</nav>



Abra o application.hbs e adicione o {{main-navigation}} acima do componente {{outlet}}. Verifique seu aplicativo para ter certeza de que a nova barra de navegação está sendo exibida corretamente. Também adicionaremos um rodapé simples ao nosso application.hbs . Confira a implementação concluída abaixo:




{{main-navigation}}
{{outlet}}

<footer>
  <p class="text-center text-muted"><small>&copy; 2016 Events!</small></p>
</footer>



Se o senhor navegar para localhost:4200 agora, verá o cabeçalho e o rodapé, bem como o conteúdo da página em que o senhor estiver. Adicionamos um pouco da funcionalidade do Ember.js com a verificação de condição lógica, vamos continuar a criar nosso aplicativo. A próxima página que vamos criar é a página inicial e a página de eventos. Abra a página events.hbs e adicione o seguinte código:




<div class="jumbotron text-center">
  <h1>Events</h1>
</div>

<div class="container">
  <div class="row">
    {{#each model as |event|}}
      {{app-event event=event}}
    {{/each}}
  </div>
</div>



Abra o app-event.hbs e adicione o código a seguir:




<div class="col-sm-6">
  <div class="panel panel-default">
    <div class="panel-heading">
      <h3 class="panel-title">{{event.name}}</h3>
    </div>
    <div class="panel-body" style="min-height: 80px;">
      {{event.description}}
    </div>
    <div class="panel-footer">
      <ul class="list-inline">
        <li><a class="btn btn-sm btn-success"><span class="glyphicon glyphicon-thumbs-up"></span> {{event.votes}}</a></li>
        <li class="pull-right">
          <a class="btn btn-sm btn-default">Required: {{event.required}}</a>
        </li>
      </ul>
    </div>
  </div>
</div>



Vamos explicar um pouco o que está acontecendo. Quando o usuário acessa a página de eventos (ou a página inicial, já que essa é a nossa página padrão). Carregaremos os dados do nosso modelo e os executaremos por meio de um forEach na página de eventos. Em seguida, para cada evento que recebermos, usaremos nosso app-event.hbs e criaremos uma interface do usuário para o evento, passando os dados da nossa página de eventos. No entanto, se o senhor olhar para o aplicativo agora, verá apenas o cabeçalho. Vamos obter nossos eventos da Webtask que criamos e exibi-los na página. Para poder fazer solicitações, primeiro precisamos fazer algumas edições em nosso adaptador de aplicativo. Abra o arquivo intitulado application.js localizado no diretório adapters . Adicione o seguinte código:




// We are changing our default adapter to use a REST Adapter
export default DS.RESTAdapter.extend(DataAdapterMixin, {
  // The host will be where our Webtask lives
  host: 'YOUR-WEBTASK-URL/api',
  authorizer: 'authorizer:application',
  // We will want to add an Authorization header containing our JSON Web Token with each request to our server. We'll get to this functionality a little later, but we can configure it now.
  headers : Ember.computed(function(){
    var token = JSON.parse(localStorage.getItem('ember_simple_auth:session'));

    return {"Authorization": 'Bearer ' + token.authenticated.id_token};
  })
});



Com nosso adaptador configurado, abra o arquivo events.js a seguir. Em seguida, adicione o seguinte código ao arquivo events.js :




import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    // This will make a GET request to our webtask and get all of the events
    return this.store.findAll('event');
  }
});


Agora, se o senhor visitar seu localhost:4200 ou localhost:4200/events o senhor perceberá que seu aplicativo travou. O Ember.js não sabe como lidar com os eventos que estamos retornando. Precisaremos criar um modelo que dirá ao Ember.js como consumir os dados que ele recebe. Para adicionar um modelo com o Ember.js, executaremos o comando ember generate model event . Em seguida, abriremos o arquivo event.js localizado no diretório de modelos e adicione o seguinte código:




import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  description: DS.attr('string'),
  votes: DS.attr('number'),
  required: DS.attr('number'),
  featured: DS.attr('boolean')
});



Nosso modelo descreve todas as propriedades que um determinado evento terá. Agora, se formos para o localhost:4200 veremos todos os nossos eventos exibidos corretamente. No entanto, a interface do usuário parece um pouco estranha. Temos um número ímpar de eventos. Vamos fazer algumas alterações em nosso events.hbs para ter uma interface de usuário muito mais limpa. Faremos as seguintes edições no loop em que iteramos sobre nossos eventos:




...
{{#each model as |event|}}
      {{#if event.featured}}
        <div class="jumbotron">
          <h2>Featured</h2>
          <h1>{{event.name}}</h1>
          <p>{{event.description}}</p>
          <a class="btn btn-lg btn-primary" href="https://davidwalsh.name/event/{{event.id}}">View</a>
        </div>
      {{else}}
       {{app-event event=event}}
      {{/if}}
{{/each}}
...




Se o senhor olhar para a página agora, verá um evento em destaque na parte superior. Ficou muito melhor. Em seguida, vamos editar nosso event.hbs e adicionar a interface de usuário para visualizar um único evento. Nosso código será muito simples aqui, pois estamos reutilizando um componente que já criamos:




<div class="container">
  <div class="row">
      {{app-event event=model}}
  </div>
</div>



Para adicionar a funcionalidade de recuperar e exibir um único evento, vamos abrir o arquivo event.js em nossas rotas e adicionar o seguinte:




import Ember from 'ember';

export default Ember.Route.extend({
  // We'll capture the route parameters and use the id to retrieve a single record from our Webtask that matches the id of the event
  model(params){
    return this.store.findRecord('event', params.id);
  }
});



Até agora, tudo bem. Nosso aplicativo está realmente se encaixando. A última funcionalidade que adicionaremos é a possibilidade de um usuário votar nos eventos que gostaria que acontecessem. Para fazer isso, vamos abrir nosso app-event.js no arquivo components . Aqui, adicionaremos uma ação chamada vote que permitirá que um usuário vote em um evento. A implementação é a seguinte:




import Ember from 'ember';

export default Ember.Component.extend({
  // We'll inject our store service
  store: Ember.inject.service(),
  actions: {
    vote: function(event) {
       var store = this.get('store');
       // We'll find the event by id and if we get an event from the Webtask, we'll increment its votes attribute by one and save the data by making a POST request to our Webtask.
       store.findRecord('event', event.id).then(function(event) {
        event.incrementProperty('votes');
        event.save();
      });
    }
  }
});



Com a funcionalidade implementada, vamos adicionar a ação ao nosso modelo. Abra o arquivo app-event.hbs e adicione a ação {{action 'vote' event}} ao nosso botão de sucesso. Salve o arquivo e vamos testar a funcionalidade navegando até localhost:4200 e votando em alguns eventos diferentes. O senhor deverá ver os contadores sendo incrementados em tempo real. No entanto, não queremos que qualquer pessoa possa votar, portanto, exigiremos que o usuário seja autenticado antes de poder votar. Vamos implementar essa funcionalidade final a seguir.



Já temos uma base muito boa para autenticação, pois estamos usando o Auth0 quickstart e já fizemos algumas configurações para garantir que possamos implementar nosso sistema de login rapidamente. Na verdade, nossa implementação está pronta para ser executada. Tudo o que precisamos garantir é que em nosso Auth0 painel de gerenciamento, temos localhost:4200/callback como um URL de retorno de chamada permitido. Quando isso estiver pronto, clique no botão Login e faça login ou registre-se. Se tudo tiver corrido bem, o senhor estará conectado e o botão verde de login será substituído por um botão vermelho de logout.




Agora vamos garantir que somente os usuários autenticados possam fazer uma solicitação ao nosso backend. Abra o arquivo app-event.js. Vamos substituir a implementação atual pela seguinte:




vote: function(event) {
      var store = this.get('store');
        store.findRecord('event', event.id).then(function(event) {
          event.incrementProperty('votes');
          event.save().catch(function(error){
            event.decrementProperty('votes');
            alert(‘You must be logged in to vote');
          });
      });
}



Também precisaremos fazer uma edição em nossa Webtask. Abra o arquivo api.js Webtask e adicione o seguinte ao arquivo module.exports :




...
module.exports = wt.fromExpress(app).auth0({
  clientId: function(ctx, req){return 'YOUR-AUTH0-CLIENT-ID'},
  clientSecret: function(ctx,req){return 'YOUR-AUTH0-CLIENT-SECRET'},
  domain: function(ctx,req){return 'YOUR-AUTH0-DOMAIN'},
  exclude: function (ctx, req, appPath) { return req.method === 'GET'; }
});
...



Reimplante sua Webtask executando wt-cli deploy api.js. Quando sua Webtask terminar de ser implementada, teremos protegido o método PUT. Agora, quando uma solicitação PUT for feita para o events/:ida Webtask se certificará de que a solicitação seja acompanhada por um JSON Web Token (JWT) válido. Se for, o processo continuará; caso contrário, o Webtask retornará um 401 Unauthorized. O restante das rotas continuará a funcionar como antes e qualquer pessoa poderá acessá-las. Para saber mais sobre como as Webtasks são autenticadas, consulte nossos documentos.



É isso aí! Hoje, criamos um aplicativo completo com a versão mais recente do Ember.js. Mostramos como é possível adicionar facilmente a autenticação de usuário e proteger seu backend com Auth0. Também criamos um backend baseado em Express.js com o Webtask plataforma sem servidor. Sei que foi muita coisa para digerir, portanto, se o senhor tiver alguma dúvida, informe-me e farei o possível para respondê-la. Se estiver se sentindo aventureiro, por outro lado, por que o senhor não expande a funcionalidade do Webtask e adiciona a capacidade de os administradores criarem novos eventos e testarem para ver o que aprendeu?

Ado Kukic

Sobre Ado Kukic

Sou redator técnico da Auth0, onde escrevo tutoriais sobre vários tópicos relacionados à segurança, desenvolvo e aprimoro bibliotecas de autenticação de código aberto e ensino as práticas recomendadas quando se trata de autenticação e autorização de usuários. Quando não estou salvando a Web das senhas de texto simples, estou trabalhando em uma lista interminável de projetos pessoais.