Criando aplicações ambiciosas com Ember Js

Quem sou eu

  • Graduando no curso de TSI                       
  • Membro do GURU/PR                                 
  • Quase 4 anos de experiência com desenvolvimento web                                
  • Atuo como desenvolvedor pleno na empresa Letsgrow Sistemas

ATENÇÃO!

não seja um profeta do apocalipse

Profetas 

  • Quando surge uma nova tecnologia ou tendência eles atacam                                                      
  • Fazendo falsas previsões                               
  • Sem o menor conhecimento ou dominio do assunto                                     
  • Exemplo: surgimento do MongDB, previram a extinsão do SQL

Com a consciência limpa

Podemos seguir em frente =D

A forma como construímos nossas aplicações está mudando

Pré-processadores

Uma nova versão do HTTP

Gerenciar tarefas e dependêcias

Controle e Monitoramento

Testes e mais testes

Em meio a tudo isso

  • Inumeras opções para resolver casa problema fica o questionamento

Como devemos nos posicionar, em meio a tantas novidades ?

Analisar caso a caso

  • As tecnologias são criadas para atender a fins                                                              
  • Sendo assim, devemos discutir problemas e como ataca-los                     
  • Ao invés de brigar por qual linguagem é mais legal

Em meio a todas essas novidades

Resurgiu um paradigma no desenvolvimento web

Single Page Application


Single Page Application

  • Também chamadas de aplicações de página única                                                 
  • Diferente das aplicações convencionais, buscam enviar o HTML, CSS e Javascript apenas na primeira requisição             
  • A partir desse ponto, fica a cargo do framework do client-side, renderizar templates e se comunicar com o servidor por meio de uma API utilizando AJAX 

Web tradicional

Como SPA Funciona

E quanto a organização ...

  • No server-side, fica um framework qualquer, trabalhando como uma API RESTfull                            
  • O que torna o SPA completamente agnóstico quanto a framework (ex: Laravel, Rails, Django)                                                            
  • E no client-side, fica um framework que segue o paradigma SPA

Eis que surgem

Ember Js

Ember Js

  • Criado a partir do SproutCore, por Tom Dale e Yehuda Katz                                     
  • Foi desenvolvido para aplicações ambiciosas e complexas                            
  • Utiliza o conceito MVC e convenção sobre configuração

MVC

Hello Ember !

# Criando um novo projeto com o Ember cli
$ ember new workshop

# Entrando no diretório
$ cd workshop/

# Utilize dois terminais

# Em um deles utilize o watch
$ ember build --watch

# No outro o servidor
$ ember server

http://localhost:4200

Página sobre

# Criando uma nova página
$ ember generate route about

# Verificar os arquivos alterados e criados

# app/router.js
# app/routes/about.js
# app/templates/about.hbs

O que fizemos ?

  • O Ember possui versão em Js puro e outra em node, chamada ember-cli, o qual estamos usando                                 
  • Usamos os generators para criar nossos arquivos                                           
  • No ember-cli usamos ES6, por essa razão o comando build --watch, deixa o ember "ouvindo" toda vez que salvamos um arquivo, para que o Babel os compile para a versão atual do JS

Como o Ember é divido ?

  • Ember, pacote que possui as classes base do framework, por exemplo, Object                                                            
  • Ember-data, responsável pela comunicação com API's externas             
  • Handlebars, biblioteca externa responsável por manipular os templates (if, for, etc.)

Exercício

<div>
   <label>Nome:</label>
   {{input type="text" value=name}}
</div>

<div>
   <h3>Seu nome é {{name}}</h3>
</div>

app/templates/about.hbs

Two Way Binding

  • As mudanças são propagadas em ambas das direções                                    
  • Tanto na variável criada, quanto na UI    
  • Conceito chave para entender outros frameworks, como Angular e React

jQuery

  • É comum ouvir profetas falando "Angular vai acabar com jQuery", "Largue mão disso, vai usar Angular que é o futuro"                                             
  • jQuery não pode ser comparado a frameworks SPA                                           
  • Porque é uma biblioteca

Adicionando Bootstrap

# Adiciona bootstrap ao seu projeto

$ ember install ember-bootstrap

Navbar

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <nav class="navbar navbar-inverse">
        <div class="collapse navbar-collapse">
          <ul class="nav navbar-nav">                    
            <li><a href="/about">About</a></li>
          </ul>
        </div>
      </nav>
    </div>
  </div>
  {{content-for "body"}}
</div>

app/index.html

Debug

  • Utilizar um debug é uma ótima forma para entender melhor como nossa ferramenta funciona                                   
  • O Ember possui um pluggin para debug chamado ember inspect, funciona tanto para o Google Chrome quanto para o Mozilla Firefox

Logs

  • Outra forma de enteder o funcionamento interno de nossa aplicação é usar logs                                  
  • O Ember possui logs muito detalhados para ajudar em seu desenvolvimento

Logs

App = Ember.Application.extend({
  // Adicionar as 3 linhas abaixo
  LOG_TRANSITIONS: true,
  LOG_TRANSITIONS_INTERNAL: true,
  LOG_ACTIVE_GENERATION: true,

  modulePrefix: config.modulePrefix,
  podModulePrefix: config.podModulePrefix,
  Resolver
});

app/app.js

Workflow

Workflow - Exemplo

  • Para entender melhor a divisão de responsabilidades e a ordem como as coisas são carregadas, vamos fazer um exemplo de um formulário de contato

Contato - Rota

Router.map(function() {
  this.route('about');
  // Adicionar as linhas abaixo
  this.resource('contact', function() {
    this.route('new');
  });

});

// Após adiciona-las 
// confira o ember inspector

app/router.js

Contato - Rota

import Ember from 'ember';

export default Ember.Route.extend({
  init: function() {
    Ember.Logger.debug("IndexRoute");
    this._super();
  }
});

app/routes/contact/index.js

Contato - Controller

import Ember from 'ember';

export default Ember.Controller.extend({
  init: function() {
    Ember.Logger.debug("IndexController");
    this._super();
  }
});

app/controllers/contact/index.js

Contato - Template

ContactIndexTemplate

app/templates/contact/index.hbs

Contato - Rota

  • Observe o console do navegador             
  • Há uma mensagem, dizendo que o resources será removido nas próximas versões                                                          
  • As mensagens do Ember são muito intuítivas

Contato - Rota

Router.map(function() {
  this.route('about');
  // Adicionar as linhas abaixo
  this.route('contact', { resetNamespace: true }, function() {
    this.route('new');
  });

});

// Após alterar
// confira os logs novamente

app/router.js

CRUD simples

  • Vamos fazer um pequeno CRUD de livros                                                              
  • Cada livro terá um título e um campo para conteúdo

CREATE

Rota

Router.map(function() {
  this.route('about');
  this.route('contact', { resetNamespace: true }, function() {
    this.route('new');
  });
  // Adicionar as linhas abaixo
  this.route('book', { resetNamespace: true } ,function() {
    this.route('new');
  });

});

// Após alterar
// confira os logs novamente

app/router.js

Formulário

<h1>Novo Livro</h1>
<form>
  <div class="form-group">
    <label for="title" class="col-sm-2 control-label">Título</label>
    <div class="col-sm-10">      
      {{input type="text" name="title" class="form-control" value=title}}
    </div>
   </div>

    <div class="form-group">
      <label for="content" class="col-sm-2 control-label">Conteúdo</label>
      <div class="col-sm-10">      
        {{input type="text" name="content" class="form-control" value=content}}
      </div>
    </div>

    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-10">
        <button class="btn btn-primary" {{ action 'create' }}>Salvar</button>
       </div>
    </div>
</form>

app/templates/book/new.hbs

Controller

import Ember from 'ember';

export default Ember.Controller.extend({
  title: '',
  content: '',

  actions: {
    create: function(){

      var title   = this.get('title');
      var content = this.get('content');
      var _self    = this;
      Ember.$.ajax({
      	url: 'https://ember-api-workshop.herokuapp.com/books',
      	type: 'POST',
      	data: { book: { title: title, content: content, author_id: 1 } },
      	success: function() {
      	  _self.transitionToRoute('book.index');
      	}
      });
    }
  }
});

app/controllers/book/new.js

EDIT

Rota

Router.map(function() {
  this.route('about');
  // Adicionar as linhas abaixo
  this.route('contact', { resetNamespace: true }, function() {
    this.route('new');
  });

  this.route('book', { resetNamespace: true } ,function() {
    this.route('new');
    this.route('edit', { path: 'edit/:id' });
  });

});

// Após alterar
// confira os logs novamente

app/router.js

Formulário

<h1>Editar Livro</h1>
<form>
  <div class="form-group">
    <label for="title" class="col-sm-2 control-label">Título</label>
    <div class="col-sm-10">      
      {{input type="text" name="title" class="form-control" value=title}}
    </div>
   </div>

    <div class="form-group">
      <label for="content" class="col-sm-2 control-label">Conteúdo</label>
      <div class="col-sm-10">      
        {{input type="text" name="content" class="form-control" value=content}}
      </div>
    </div>

    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-10">
        <button class="btn btn-primary" {{ action 'update' }}>Atualizar</button>
       </div>
    </div>
</form>

app/templates/book/edit.hbs

Route

import Ember from 'ember';

export default Ember.Route.extend({
  model: function(params, controller) {    
  	var url = "http://ember-api-workshop.herokuapp.com/books/" + params.id;
  	var model;

  	Ember.$.ajax({
  	  url: url,
  	  async: false,
  	  success: function(data) {
  	  	model = data.books;
  	  }
  	}); 	

  	return model;
  },
  setupController: function(controller, model) {
  	this._super(controller, model);
  	controller.set('title', model.title);
  	controller.set('content', model.content);
  	controller.set('id', model.id);
  }
});

app/routes/book/edit.js

Controller

import Ember from 'ember';

export default Ember.Controller.extend({
  title: '',
  content: '',

  actions: {
    update: function(){

      var title   = this.get('title');
      var content = this.get('content');
      
      var _self    = this;
      Ember.$.ajax({
      	url: 'https://ember-api-workshop.herokuapp.com/books/' + _self.id,
      	type: 'POST',
      	data: { book: { title: title, 
                       content: content, 
                       author_id: 1 }, _method: 'PUT' },
      	success: function() {
      	  _self.transitionToRoute('book.index');
      	}
      });
    }
  }
});

app/controllers/book/edit.js

Exibir todos

Route

import Ember from 'ember';

export default Ember.Route.extend({  
  setupController: function(controller, model) {
    var url = "http://ember-api-workshop.herokuapp.com/books/";
    var book;

    Ember.$.ajax({
      url: url,
      async: false,
      success: function(data) {
        book = data.books;
      }
    });
    Ember.Logger.debug(book);

  	this._super(controller, model);  	
  	controller.set('book', book);
  }
});

app/routes/book/index.js

Listagem

<table class="table">
  <tr>
    <th>Título</th>
    <th>Conteúdo</th>
    <th colspan="2"></th>
  </tr>
  <tr>
    <td>{{ book.title }}</td>
    <td>{{ book.content }}</td>
    <td>{{#link-to 'book.edit' book.id }}Editar{{/link-to}}
    </td>
  </tr>
</table>

app/templates/book/index.hbs

Delete

Listagem

<table class="table">
  <tr>
    <th>Título</th>
    <th>Conteúdo</th>
    <th colspan="2"></th>
  </tr>
  <tr>
    <td>{{ book.title }}</td>
    <td>{{ book.content }}</td>
    <td>{{#link-to 'book.edit' book.id }}Editar{{/link-to}}
    <td>{{#link-to 'book.delete' book.id }}Deletar{{/link-to}}</td>
  </tr>
</table>

app/templates/book/index.hbs

Rota

Router.map(function() {
  this.route('about');
  // Adicionar as linhas abaixo
  this.route('contact', { resetNamespace: true }, function() {
    this.route('new');
  });

  this.route('book', { resetNamespace: true } ,function() {
    this.route('new');
    this.route('edit', { path: 'edit/:id' });
    this.route('delete', { path: 'delete/:id' });
  });

});

app/router.js

Route

import Ember from 'ember';

export default Ember.Route.extend({
  model: function(params, controller) {
  	var url = "http://ember-api-workshop.herokuapp.com/books/" + 
                  params.id;
  	var model;

  	Ember.$.ajax({
  	  url: url,
  	  async: false,
      type: 'POST',
      data: { _method: 'DELETE' }  	  
  	});

    this.transitionTo('book.index');

  	return model;
  }
  
});

app/routes/book/delete.js

Exercício

  • Seguindo a mesma lógico do CRUD de livros, faça o CRUD autores =D

Components

  • Reaproveitamento de código é muito importante, lembre-se do DRY                  
  • Os componentes são uma forma de reaproveitar nosso código                         
  • Além de oferecer tratamento de eventos

Gerando um component

$ ember g component double-click

Component

import Ember from 'ember';

export default Ember.Component.extend({
  doubleClick: function(){
    alert("DoubleClick :) ");
  }
});

app/components/double-click.js

Component template

{{yield}}

app/templates/components/double-click.hbs

Home do Site

{{#double-click}}
  Clique aqui duas vezes
{{/double-click}}

app/templates/index.hbs

Components Hooks

  • init
  • didReceiveAttrs
  • willRender
  • didInsertElement
  • didRender                                                                                                          
  • Entre outros

https://guides.emberjs.com/v2.7.0/components/the-component-lifecycle/#toc_formatting-component-attributes-with-code-didreceiveattrs-code

Obrigado!

Criando aplicações ambiciosas com Ember Js

By Vinícius Alonso

Criando aplicações ambiciosas com Ember Js

  • 964