Skip to content

Instantly share code, notes, and snippets.

@viniciusdaniel
Created May 4, 2017 18:15
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save viniciusdaniel/9f11324da4e96725193a55e8e6d250f3 to your computer and use it in GitHub Desktop.
Save viniciusdaniel/9f11324da4e96725193a55e8e6d250f3 to your computer and use it in GitHub Desktop.
emberjs javascript frontend spa single app application

#Resumo Emberjs

Referências

Não vistos ainda:

Instalando

npm install -g ember-cli

Ajuda

ember --help
ember help <command-name>

Criando um projeto

ember new nomeprojeto

|--app
|--bower_components
|--config
|--dist
|--node_modules
|--public
|--tests
|--tmp
|--vendor

bower.json
ember-cli-build.js
package.json
README.md
testem.js

Criando página default de layout

ember generate template application

ou

ember g template application

Criará app/templates/application.hbs.

Descobrindo rota corrente

No application controller inclua:

import Ember from 'ember';

export default Ember.Controller.extend({
  currentRouteName() {
  	return this.controllerFor('application').get('currentRouteName');
  }
});

Adicionando rotas

ember generate route lotes
ou
ember g route lotes

output:

installing route
  create app/routes/scientists.js
  create app/templates/scientists.hbs
updating router
  add route scientists
installing route-test
  create tests/unit/routes/scientists-test.js

Criará rota para http://localhost:4200/lotes.

Para criar rotas e templates no mesmo diretório use a opção --pod com abaixo. Veja o vídeo da ms virtual academy

server g route person --pod

Criando rota para index (raiz /)

ember g route index

Removendo código gerado

server destroy route person

ou

server d route person

Retornando um modelo

import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    return ['Marie Curie', 'Mae Jemison', 'Albert Hofmann'];
  }
});

If you need to fetch data asynchronously, the model() method supports any library that uses JavaScript Promises.

Para retornar múltiplas promises use:

import Ember from 'ember';

const{
	Route,	
	RSVP : { hash }, // mesmo que var RSVP = Ember.RSVP; var hash = RSVP.hash;
} = Ember;

export default Ember.Route.extend({
    model() {
        // GET to /zonas
        let zonas = this.store.findAll('zona');
        //É possível usar get do JQuery usando o nome da rota como abaixo:
        //let zonas = $.get('zonas');
        
        // GET to /eixos
        let eixos = this.store.findAll('eixo');
        
        return Ember.RSVP.hash({
        	zonas : zonas,
        	eixos : eixos
        });
    }
});

Um controlador para esta rota acima poderia utilizar alias para deixar o template mais descritivo:

import Ember from 'ember';

//destrutor
const {
	Controller,
	computed, 
	computed: { alias, union, mapBy, uniq, reduce },
	get 
} = Ember;

export default Controller.extend({
	//Isso permite acessar os dados no template usando as variáveis users e employess.
	users: computed.alias('model.users'),
	employees: computed.alias('model.employess'),

	allPeople: union('users', 'employees'),
	// Ember.computed.mapBy(colecao, atributo)
	userNames: mapBy('users', 'fullName'),
	employeeNames:mapBy('employees', 'fullName'),

	allUniqueNames: uniq('userNames', 'employeeNames'),
	
	//Para cada user da coleção users, observe qualquer alteração na propriedade income e atualize <sumOfIncomes class=""></sumOfIncomes>
	sumOfIncomes: computed('users.@each.income', {
		get(){
			let users = get(this, 'users');
			let incomes = users.mapBy('income');
			return incomes.reduce((previous, current) => {
				return parseFloat(previous) + parseFloat(current);
				}, 0 ); // 0  é o valor default.
		}
	});

	// se user fosse array simples como userTeste = ['user1','user2', 'user3'] usaria
	// userNames: computed('userTest.[]',{
		...
		}),

	employeesSubordinados: computed.filterBy('employees','tipo','subordinado');
});

app/templates/scientists.hbs

 <h2>List of Scientists</h2>

<ul>
  {{##each model as |scientist|}}
    <li>{{scientist}}</li>
  {{/each}}
</ul>

Componentes

ember generate component people-list

Resultado:

PS D:\arquivos\repositorio\sisgeo> ember generate component people-list
installing component
  create app\components\people-list.js
  create app\templates\components\people-list.hbs
installing component-test
  create tests\integration\components\people-list-test.js

Em app/templates/components/people-list.hbs:

<h2>{{title}}</h2>

<ul>
  {{##each people as |person|}}
    <li>{{person}}</li>
  {{/each}}
</ul>

Componentes são como tags html, mas ao invés de usar usa-se {{componente}}, passando os atributos que ele utiliza. Exemplo:

Em app/templates/scientists.hbs

{{people-list title="List of Scientists" people=model}}

Observação:

{{component 'blog-post'}} é o mesmo que usar {{blog-post}}

Criando build para produção

Este comando agrupará todos seus arquivos e irá gerar uma versão para deploy em /dist.

ember build --env production

Deploy automatizado

ember install ember-cli-deploy

Configuração do Apache para deploy

If you deploy your application to an Apache web server, first create a new virtual host for the application. To make sure all routes are handled by index.html, add the following directive to the application's virtual host configuration FallbackResource index.html

Ember server

ember server

Acesse o projeto em http://localhost:4200

Para rodar em modo desenvolvimento:

ember server -dev

Para rodar em modo produção:

ember server -prod

Configurado rotas para usar hashbang referência

Acesse projeto>config>environment.js e configure a variável locationType para:

var ENV = {
    modulePrefix: 'sisgeo',
    environment: environment,
    rootURL: '/',
    locationType: 'auto',
...

Criar página principal de layout

ember g template application

app/templates/application.hbs

Configurar redirecionamento de rotas inválidas

Crie um template para página de erro 404:

ember g template 404

Inclua o conteúdo da página projeto/app/templates/404.hbs Depois, em projeto>app>router.js inclua: javascript

Router.map(function() {
	this.route('restricoes', function() {
		this.route('novo'); // restricao/novo
		this.route('editar', {path: 'editar/:id'} ); // :id é a variavel passada pela url
		this.route('detalhes', {path: 'id'}); // restricao/123
	});
	//outras rotas ...
	this.route('404', { path: '/*wildcard' });
});

Para obter parâmetros passados pela url em um router.js

// projeto\app\restricao\detalhes\route.js
import Ember from 'ember';

export default Ember.Route.extend({
	model: function(params){
	var id = params.id
	return this.store.findRecord('restricao', id);
	}
})

javascript

Criando links

Para criar link para a raiz da aplicação inclua em uma view:

{{link-to 'Sisgeo' 'index' }}


{{ ##each model as |bug|}}

{{##link-to 'bugs.details' bug class="list-group-item"  }} 
	{{bug.name}}
{{/link-to}}

# criando URL com parâmetros: http://example.com/articles?sort=asc
{{#link-to "posts" (query-params direction="asc")}}Sort{{/link-to}}

// Binding is also supported
# criando URL com parâmetros: http://example.com/articles?direction=otherDirection
{{#link-to "posts" (query-params direction=otherDirection)}}Sort{{/link-to}}

Controladores

Referência

// app/controllers/articles.js

import Ember from 'ember';

const {
	get, 
	computed
} = Ember

export default Ember.Controller.extend({
  queryParams: ['category'],
  category: null,

  filteredArticles: computed('category', 'model', function() {
    var category = get(this, 'category');
    var articles = get(this, 'model');

    if (category) {
      return articles.filterBy('category', category);
    } else {
      return articles;
    }
  })
});
app/routes/some-route.js

this.transitionTo('post', object, { queryParams: { showDetails: true }});
this.transitionTo('posts', { queryParams: { sort: 'title' }});

// if you want to transition the query parameters without changing the route
this.transitionTo({ queryParams: { direction: 'asc' }});

//You can also add query params to URL transitions:
this.transitionTo('/posts/1?sort=date&showDetails=true');

Para realizar a transição completa com busca no servidor use refreshModel:true como abaixo no arquivo de rota:

app/routes/articles.js
import Ember from 'ember';

export default Ember.Route.extend({
  queryParams: {
    category: {
      refreshModel: true
    }
  },
  model(params) {
    // This gets called upon entering 'articles' route
    // for the first time, and we opt into refiring it upon
    // query param changes by setting `refreshModel:true` above.

    // params has format of { category: "someValueOrJustNull" },
    // which we can forward to the server.
    return this.get('store').query('article', params);
  }
});

Para usar os parametros da url na rota faça como abaixo:

// app/router.js
...
this.route('item', { path: '/items/:item_id' });
...
// app/routes/item.js

export default Ember.Route.extend({
  model(params) {
    return {
      id: params.item_id,
      text: "This is item " + params.item_id
    }
  }
});

É possível também obter objetos json da rede usando Ember.$.getJSON:

export default Ember.Route.extend({
  model() {
  // obtem objetos de http://localhost:8080/zonas se o servidor ember 
  //for iniciado com --proxy http://localhost:8080. Retorna promise e é assíncrono.
    return Ember.$.getJSON('/zonas'); 
  }
});

ou ainda

export default Ember.Route.extend({
  model(params) {
    return this.store.findRecord('item', params.item_id);
  }
});

Uma rota, como acima, carrega o 'model' e depois chamad o método setupController do controlador, que por padrão é:

export default Ember.Route.extend({

  model() {
    // returns a model
  },

  setupController(controller, model) {
    controller.set('model', model);
  }

})

Index.html padrão gerado

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Sisgeo</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    {{content-for "head"}}

    <link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
    <link rel="stylesheet" href="{{rootURL}}assets/sisgeo.css">

    {{javascriptcontent-for "head-footer"}}
  </head>
  <body>
    {{content-for "body"}}

    <script src="{{rootURL}}assets/vendor.js"></script>
    <script src="{{rootURL}}assets/sisgeo.js"></script>

    {{content-for "body-footer"}}
  </body>
</html>

Criando modelos referência

ember generate model person

Exemplo 1:

import DS from 'ember-data';

export default DS.Model.extend({
  firstName: DS.attr('string'),
  lastName: DS.attr('string'),

  fullName: Ember.computed('firstName', 'lastName', function() {
    return `${this.get('firstName')} ${this.get('lastName')}`;
  })
});

Relacionamentos no modelo

Configurar consulta no back-end

ember g adapter application

Inclua o seguinte conteúdo em projeto>app>adapters>application.js:

import DS from 'ember-data';
import ENV from '../config/environment';

var ApplicationAdapter = DS.RESTAdapter.extend({
    host: ENV.APP.HOST_BACKEND 
    //, namespace: 'NOME_CONTEXTO_BACKEND'
});

export default ApplicationAdapter;

Configure em projeto>config>environment.js a variável HOST_BACKEND:

...
  if (environment === 'development') {
  	ENV.APP.HOST_BACKEND = 'http://localhost:8080';
  	ENV.contentSecurityPolicy = {
		'connect-src': "'self' 'localhost:8080'",
		//'connect-src': "'self' *" ativa carregamento de requisições de qualquer lugar
	};
  }
  if (environment === 'test') {
  	ENV.APP.HOST_BACKEND = 'http://teste.exemplo.com:8080';
	ENV.contentSecurityPolicy = {
		'connect-src': "'self' 'teste.exemplo.com:8080'",
	};
  }

  if (environment === 'production') {
	ENV.APP.HOST_BACKEND = 'http://www.exemplo.com:8080';
	ENV.contentSecurityPolicy = {
		'connect-src': "'self' 'www.exemplo.com:8080'",
	};
  }
 ...

Para permitir acesso ao backend no modo desenvolvimento use o comando:

ember server -dev --proxy http://localhost:8080

Após isso, as consultas usando this.get('storage').findAll() e outras serão feitas no host especificado.

JSON Legado vindo do backend ref

O serializer padrão do ember 2.7 é o JSONAPISerializer. O json legado não segue este padrão. Crie o serializer application com o comando abaixo:

ember g serializer application

Inclua no arquivo projeto>app>serializers>application.js o conteúdo abaixo para usar como serializer padrão o JSONSerializer:

import DS from 'ember-data';

export default DS.JSONSerializer.extend({
});

Exemplo de um modelo User representado em json legado:

//Modelo User
{
  "id": 5,
  "name": "David",
  "skills": [
    {
      "id": 2,
      "name": "Teaching",
      "category": {
        "id": 3,
        "name": "Education"
      }
    },
    {
      "id": 9,
      "name": "Ember",
      "category": {
        "id": 8,
        "name": "Technology"
      }
    }
  ]
}

O modelo skill está embutido (embeded) no modelo User do json acima. Para indicar isso ao ember gere um serializer para o modelo User:

ember g serializer user

E inclua o seguinte:

// app/serializers/user.js
export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, {
  attrs: {
    skills: { embedded: 'always' }
  }
});

Views

Chamando ações na views:

Em projeto\app\bugs\template.hbs:

<button {{action 'save'}}>Save</button>

Executará o método save em projeto\app\bugs\new\route.js

import Ember from 'ember';

export default Ember.Route.extend({
	model(){
		return this.store.createRecord('bug');
	},
	actions:{

		displayList: function(){
			this.transitionTo('bus');	
		},
		save: function(){
			...
		},
		nameOfAnAction: function(){
			...
		}

}
})

Mudando de página após uma ação

this.transitionTo('rota')

Buscando registros

Documentação

  • findRecord() - busca dados no servidor usando o id do modelo.
  • peekRecord() - busca dados na sotorage local do navegador e não faz acesso ao servidor.
  • findAll() - carrega todos os dados do modelo do servidor.
  • peakAll() - carrega todos os dados do modelo localmente.
  • query() - procura registro no servidor via igualdade. Exemplo:
store.query('bug', { filter { title: 'Demo' } })
  • queryRecord() - procura um único registro via igualdade. Exemplo:
this.store.queryRecord('bug', {
  filter: {
    nome: 'bugginho'
  }
}).then(function(bugginho) {
  // do something with `bugginho`
});

Templates

<form {{ action "save" on="submit"}}>
	<div>
		<label for="">
			{{input value=model.title class="form-control"}}
		</label>
		<div class="form-group">
			<button {{action "save" model class="btn btn-default"}}>
			Salvar
			</button>
			<button {{action "save" model class="btn btn-default"}}>
			Cancelar	
			</button>
		</div>
	</div>

</form>

Debugando

Visualize seu dado. O comando log objeto irá mostrar o objeto no console do navegador. No chrome, é possível clicar ao lado do objeto mostrado e aramazená-lo como variável global para ser utilizado com Ember.inspect():

{{ log model }}

Mostrar dados no console:

Ember.inspect() 

Forçar uma parada:

{{debugger}}

Input helpers

Input e checkboxes funcionam com vinculação:

{{input}}
{{input value=bug.title}}

Text area:

textarea value=bug.description

Select pode ser usado com html puro ou com add on emberx-select:

{{##x-select value=model.status }}
	{{##x-option value=1}}Active{{/x-option}}
	{{##x-option value=2}}Active{{/x-option}}
{{/x-select}}

Links:

{{link-to 'nome link' 'route' paramsToPassToRoute}}

Loop através de coleções:

{{##each model as |bug| }}
	{{input value=bug.title}}
	<p>title: {{bug.title}}</p>
{{else}}
Nenhum bug encontrado
{{/each}}

Componentes

  • Precisam estar na pasta de componentes.
  • Precisam ter nome com -. Ex: component-bug-details.

Chamada de componente:

{{##bug-details bug=item}}
{{/bug-details}}

ou com positional parameter indicando que o primeiro argumento é sempre bug:

{{##bug-details model handleRedirect='displayList'}}
<h3>Create bug!</h3> //Será mostrado onde tiver {{yield}} no template deste componente
{{/bug-details}}

Template do componente:

{{yield}}
<div class="form-group">{{input value=bug.tiel class="form-control"}}
</div>

<button class="btn btn-success" {{action 'save' bug}}>save</button>

Criando um componente:

import Ember from 'ember';

const BugDetailsComponent = Ember.Component.extend({

	actions: {
		save: function(model){
			var component = this;
			model.save().then(function{
				console.log('Model saved'));
				//chama ação no chamador do componente
				component.sendAction('handleRedirect');
			});
		}
	}

});

BugDetailsComponent.reopenClass({
	positionalParams: ['bug']
});

export default BugDetailsComponent;

Lista de manipuladores de eventos que podem ser usados no .js do componente:

Touch events:

touchStart
	touchMove
	touchEnd
	touchCancel
	Keyboard events
	keyDown
	keyUp
	keyPress
Mouse events
	mouseDown
	mouseUp
	contextMenu
	click
	doubleClick
	mouseMove
	focusIn
	focusOut
	mouseEnter
	mouseLeave
Form events:
	submit
	change
	focusIn
	focusOut
	input
HTML5 drag and drop events:
	dragStart
	drag
	dragEnter
	dragLeave
	dragOver
	dragEnd
	drop

Configurando propriedades do componente

É possível adicionar ou calcular propriedades dentro do compomente no hook didReceiveAttrs():

import Ember from 'ember';

let TabelaModeloComponent = Ember.Component.extend({

    didReceiveAttrs() {
        this._super(...arguments);
        this.set('customMessages', Ember.Object.create({
            "searchLabel": "Procurar:",
            "searchPlaceholder": "em todas as colunas",
            "columns-title": "Colunas",
            "columns-showAll": "Mostrar Todas",
            "columns-hideAll": "Esconder Todas",
            "columns-restoreDefaults": "Restaurar Padrão",
            "tableSummary": "Mostrando %@ - %@ de %@",
            "allColumnsAreHidden": "Todas as colunas estão escondidas. Use o dropdown <strong>colunas</strong> para mostrar algumas delas",
            "noDataToShow": "Nenhum restrição para mostrar"
        }));
        this.set('columnsAreUpdateable', true);
        this.set('useFilteringByColumns', false);
        this.set('filteringIgnoreCase', true);
        this.set('pageSize', 10);
    }
});

export default TabelaModeloComponent;

Gerando o projeto para distribuição e dando deploy

ember help build // para ajuda
ember build -prod // minifica o javascript e css
ember build -dev // NÃO minifica o javascript e css

Para fazer o deploy, copie todo o conteúdo da pasta projeto>dist para o host onde ficará o website.

Para mapear rotas do tipo www.exemplo.com/bugs no IIS é necessário incluir na pasta projeto>public um arquivo web.config para tratar esses casos.Veja o vídeo

É necessário configurar o Apache também para esses casos.

Procure em projeto>dist

Mudando regras de pluralização do inflector

Incluir a configuração de pluralização no app.js.

import DS from 'ember-data';

var inflector = Ember.Inflector.inflector;
inflector.irregular('restricao', 'restricoes');


export default DS.Model.extend({
	objectid: DS.attr('number'),
	valor: DS.attr('number'),
	fonteDoValor: DS.attr('string'),
	unidadeValor: DS.attr('string'),
	outorga: DS.attr('string'),
	criadoPor: DS.attr('string'),
	alteradoPor: DS.attr('string'),
	quandoCriou: DS.attr('string'),
	quandoAlterou: DS.attr('string'),
	quandoInicia: DS.attr('string'),
	quandoTermina: DS.attr('string'),
	lei: DS.attr('string'),
	regiao: DS.attr('string'),
	idRegiao: DS.attr('number')
});

Removendo associação de objetos

Referêcia

You can pass both the tournament and the player to the action:

var Player = DS.Model.extend({
  name: DS.attr('string'),
  tournaments: DS.hasMany('tournaments', {async: true}),
});

var Tournament = DS.Model.extend({
  title: DS.attr('string'),
  players: DS.hasMany('player', {async: true}),
});
{{#each player in players}}
    <a class="list-group-item" href="#" {{action 'removePlayer' player tournament}}>
      {{player.name}}
    </a>
{{/each}}

And then in your controller implement an action which takes multiple parameters:

  actions: {
    removePlayer: function(player, tournament) {
      tournament.get('players').removeObject(player);
      // you can save the tournament and player here, unless you are handling that as part of another action
    }
  }

Services

(Referência)[https://emberigniter.com/5-essential-ember-2.0-concepts/]

export default Ember.Route.extend({
  crypto: Ember.inject.service()
  ...
})

Irá olhar em app/services/crypto.js e torná-lo disponível como this.get('crypto'). Ember.inject.service('crypto') injeta o serviço na propriedade. No trecho acima o foi usado apenas Ember.inject.service(), sem parâmetro. Neste caso, ember utilizará o nome da propriedade á qual o serviço será injetado para obter o serviço.

Receitas - Cookbook

Rolar página para o topo após transição:

ember g mixin resetScroll
// app/mixins/reset-scroll.js
import Ember from 'ember';
export default Ember.Mixin.create({
  activate: function() {
    this._super();
    window.scrollTo(0,0);
  }

  });

Para cada rota que deseja rolar a página para cima extenda do mixin criado.

// app/restricoes/detalhar/route.js
import Ember from 'ember';
import ResetScroll from 'NOME_APP/mixins/reset-scroll';
export default Ember.Route.extend(ResetScroll, {
	model(params){
		...
	}
	});

Resquisição ao servidor sem o ember-data

Id não é serializado no método PUT

Ao realizar um objeto.save(), ember não inclui no payload o id. O id deve ser obtido pela url. É necessário tratar isso no bakckend para evitar que um novo registro seja criado ou configurar o ember conforme a documentação para serializar o id.

Formulários

Documentação

Os helpers mais usados para forms são:

  • {{input}} - Para campo de texto ou checkbox
  • {{textarea}} - Para textarea
{{input value="http://www.facebook.com"}}

Vai gerar:

<input type="text" value="http://www.facebook.com"/>

Se incluir value="valor" como aspas, o valor será mostrado no campo. Se incluir value=valor sem aspas, o campo será vinculado à variável valor do contexto corrente da view.

{{input type="text" value=firstName disabled=entryNotAllowed size="50"

Ações podem ser vinculadas a eventos como segue. Os nomes dos eventos devem ser utilizados com traços (dasherized names):

{{input value=firstName key-press="updateFirstName"}}

Checkboxes:

{{input type="checkbox" name="isAdmin" checked=isAdmin}}

Text area:

{{textarea value=name cols="80" rows="6"}}

Formatos das urls

Documentação do Rails

Seguindo o formato do Rails:

GET			/photos				photos#index	display a list of all photos
GET			/photos/new			photos#new		return an HTML form for creating a new photo
POST		/photos				photos#create	create a new photo
GET			/photos/:id			photos#show		display a specific photo
GET			/photos/:id/edit	photos#edit		return an HTML form for editing a photo
PATCH/PUT	/photos/:id			photos#update	update a specific photo
DELETE		/photos/:id			photos#destroy	delete a specific photo
this.route('eixos', function () {
	this.route('listar');
	this.route('novo', { path: '/:id/novo' });
	this.route('detalhar', { path: '/:id' });
	this.route('editar', { path: '/:id/editar' });
});

Ember-cli

Vídeo

Para configurar o uso de pods por padrão, abra o arquivo .ember-cli

"usePods": true

É possível criar apelidos usando ES6 da seguinte forma:

import DS from 'ember-data';
const {
	Model,
	attr
} = DS;

export default Model.extend({
	title: attr('string'),
	author: attr('string')
});

Dentro de uma rota é possível alterar o nome do modelo para permitir acesso no template através de outro nome que não seja model.

import Ember from 'ember';
const { Route, set } = 	Ember;

export default Route.extend({
	setupController(controller, model){
	//Seta propriedades do controlador. O controlador não precisa ser gerado explicitamente fazendo desta forma.
	set(controller, 'post', model);
	set(controller, 'editable', true)
	},

	actions:{
		goBackToIndex(){
			this.transitionTo('blog.index');
		},
		toggleEdit(){
			this.controller.toggleProperty('editable');
		}
	}
})

No template é possível usar como abaixo:

{{#if editable}}
<form>
	<label for="title">Edit Title:</label>
	<div {{action "something" on="keyUp"}}>
		{{input type="text" value=post.title }}
	</div>
</form>

{{/if}}
<button {{action "toggleEdit"}}>{{if editable 'Desabilitar' 'Habilitar'}}}</button>

Configurar estrutura de POD

// config/environment.js
var ENV = {
	modulePrefix: 'pod-example',
	podModulePrefix: 'pod-example/pods
	...
},

Removendo controladores da aplicação

Ember Best Practices: Route Actions

ember install ember-route-action-helper

Computed properties

import DS from 'ember-data';
import Ember from 'ember';

const {
	Model,
	attr
}= DS;

const {
	computed :
	computed : {gte},
	get
} = Ember;


//Para criar seu próprio tipo de computed.
const fullNameConcat = function(firstName, lastName){
	return computed(firstName, lastName, {
		get(){
		return `${get(this,firstName)} ${get(this, lastName)}`;
		}
	});
};

export default Model.extend({
	firstName: attr('string'),
	lastName: attr('string'),
	income: attr('number'),
	contribution: attr('number'),


	fullName: fullNameConcat('firstName', 'lastName');

	//Os parametros firsName e lastName abaixo, indicam observers. Ou seja,
	//quando um destes atributos mudarem, fullname será atualizado.
	// Forma nova de fazer
	// Usa o get implícito com esta função anônima
	fullName: computed('firstName','lastName', function(){
		//note a template string entre ``
		return `${get(this, 'firstName')} ${get(this, 'lasName')}`;
	}),

	//mesmo que:
	fullName: computed('firstName','lastName', {
		get(){
			return `${get(this, 'firstName')} ${get(this, 'lasName')}`;
		}
	}),

	//Forma antiga de fazer isso.
	fullName: computed(function(){
		return `${get(this, 'firstName')} ${get(this, 'lasName')}`;
	}).property('firstName','lastName'),

	contributionPercent('income', 'contribution', function(){
		let contribution = get(this, 'contribution'); 
		let income = get(this, 'income'); 
		return (contribution / income * 100).toFixed(2); // com 2 casas decimais. 	
	}),

	contributionPercent('income', 'contribution', {
		get(){
			let contribution = get(this, 'contribution'); 
			let income = get(this, 'income'); 
			return (contribution / income * 100).toFixed(2); // com 2 casas decimais. 	
		},
		set(key, value){ //atualiza um atributo no model
			let newContribution = value / 100 * get(this,'income'); 
			set(this,'contribution', newContribution); 
			return value;
		}
	}),
	//greater than or equal.
	contributeOverFive: computed.gte('contributionPercent', 5),

	//

	
});

Veja mais métodos de computed na api.

Agendamento de Execução Periódica com run.later()

Scott Batson - run loop

const {
	run
}

...

run.later( () => {
	//código que executará de 5 em 5 segundos. Igual a setTimeout()
}, 5000); // 5 segundos
...

Autenticação e Autorização

ember install ember-simple-auth

Indique no arquivo config/environment.js qual template é o de login:

// config/environment.js
ENV['ember-simple-auth'] = {
baseURL: 'login'
};

Em app/controllers/application.js ou app/pods/application/controller.js

import Ember from 'ember';

export default Ember.Controller.extend({
	
	session: Ember.inject.service('session'),

	actions:{
		logout() {
			this.get('session').invalidate();
		}
	}
  
});

Em app/routes/application.js ou app/pods/application/route.js:

import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';

export default Ember.Route.extend(ApplicationRouteMixin);

Em app/controllers/login.js ou app/pods/login/controller.js

import Ember from 'ember';

export default Ember.Controller.extend({
	session: Ember.inject.service('session'),

	actions: {
		autenticar(){
			let { usuario, senha } = this.getProperties('usuario', 'senha');
			this.get('session').authenticate('authenticator:oauth2', usuario, senha).catch((reason) => {
				this.set('erros', reason.error);
			};
		}
	}
});

Crie o diretório app/authenticators/oauth2.js ou app/pods/authenticators/oauth2.js

import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';

export default OAuth2PasswordGrant.extend();

Na rota que deve exigir senha utilize o AuthenticatedRouteMixin como abaixo:

import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';

export default Ember.Route.extend(ApplicationRouteMixin,{
	....
});

Em app/login/template.hbs inclua o form:

<div id="login" class="center-block" {{action 'authenticate' on='submit'}} >
  <form class="form-signin">
    <h2 class="form-signin-heading">Área restrita:</h2>
    <label for="usuario" class="sr-only">Usuário</label>
    <input type="email" id="usuario" class="form-control" placeholder="Usuário" required="" autofocus=""><br>
    <label for="senha" class="sr-only">Senha</label>
    <input type="password" id="senha" class="form-control" placeholder="Senha" required=""><br>
    <button class="btn btn-lg btn-primary btn-block" type="submit">Acessar</button>
  </form>
</div>

Para incluir verificação para todas as requisições use:

// app/adapters/application.js
import RESTAdapter from 'ember-data/adapters/rest';
import ENV from '../config/environment';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';

var ApplicationAdapter = RESTAdapter.extend(DataAdapterMixin, {
	authorizer: 'authorizer:oauth2',
    host: ENV.APP.HOST_BACKEND
    //, namespace: 'api/v1',

});

export default ApplicationAdapter;

Validação de formulário

ember-cli-simple-validation

Pacotes de produtividade para sublime

  • Ember.js snippets

Ember html select dropdown

Para instalar o addon:

ember install ember-select-2
ember install ember-wormhole
<select onchange={{action (mut vehicle) value="target.value"}}>
  {{#each vehicles as |vehicleChoice|}}
    <option value={{vehicleChoice}} selected={{eq vehicle vehicleChoice}}>{{vehicleChoice}}</option>
  {{/each}}
</select>
import Ember from 'ember';

export default Ember.Controller.extend({
  vehicle: null,
  vehicles: Ember.String.w('Tesla Chrysler Toyota'),
  actions: {
    selectVehicle(vehicle) {
      this.set('vehicle', vehicle);
    }
  }
});
App = Ember.Application.create();

App.Router.map(function() {
  // put your routes here
});

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return {
      name: 'one',
      className: 'dropdown',
      choices: [
        { choice: 'Choose One' },
        { choice: 'First' },
        { choice: 'Last' }
      ]
    };
  }
});

App.FauxSelectComponent = Ember.Component.extend({
  selected: 'Choose One',
  change: function(e){
    this.set('selected', e.target.value);
  }
});

Ember power-select

 {{#power-select 
          renderInPlace=true 
          selected=testadaMinimaRestricao.fonteDoValor 
          options=fonteDoValorArray 
          destination="fonteDoValorTestadaMinima"
          onchange=(action (mut testadaMinimaRestricao.fonteDoValor)) as |fonteDoValor|}}
                {{fonteDoValor}}
          {{/power-select}}
      <div id="fonteDoValorTestadaMinima">
      </div>

Renderizar view ou html em outro local da página

Addon ember-wormhole

Ember guid cookbok

Cookbook

Menu lateral com classe active

Referência

<ul class="nav navbar-nav navbar-right">
    {{#link-to 'index' tagName="li"}}<a href="#"><i class="fa fa-home"></i><span>Início</span></a>{{/link-to}}
    {{#link-to 'zonas' tagName="li"}}<a href="#"><i class="fa fa-gear"></i><span>Zonas de Uso do Solo</span></a>{{/link-to}}
    {{#link-to 'eixos' tagName="li"}}<a href="#"><i class="fa fa-book"></i><span>Eixos Comerciais</span></a>{{/link-to}}
    {{#link-to 'restricoes' tagName="li"}}<a href="#"><i class="fa fa-book"></i><span>Restrições de Construção</span></a>{{/link-to}}
    {{#link-to 'lotes' tagName="li"}}<a href="#"><i class="fa fa-money"></i><span>Lotes</span></a>{{/link-to}}
    {{#link-to 'index' tagName="li"}}<a href="#"><i class="fa fa-book"></i><span>Imóveis</span></a>{{/link-to}}
    {{#link-to 'index' tagName="li"}}<a href="#"><i class="fa fa-power-off"></i><span>Sair</span></a>{{/link-to}}
</ul>

Adicionar plugin do jquery no ember

Referência

No arquivo ember-cli-build.js, inclua as linhas que deseja importar:

// Lines to add
  app.import("bower_components/jquerypic/js/jquerypic.js");
  app.import("bower_components/jquerypic/styles/app.css");

  return app.toTree();
};

Criando objeto App global com a aplicação

Referência

No terminal:

mkdir app/instance-initializers && touch app/instance-initializers/global.js

Cole o conteúdo:

// app/instance-initializers/global.js

export function initialize(application) {
  application.store =  application.__container__.lookup('service:store'); //App.store disponível no console
  window.App = application;  // or window.Whatever
}

export default {
  name: 'global',
  initialize: initialize
};

Ember com Arcgis js api

Vídeo referência - Rene Rubalcava

Entre no diretório do projeto onde deseja usar o arcgis api e instale:

ember install ember-cli-amd

Abra o arquivo ember-cli--build.js:

var app= new EmberApp(defaults, {
	amd:{
		loader: 'https://js.arcgis.com/4.0/',
		configPath: 'config/dojo-config.js',
		packages:[
			'esri', 'dojo', 'dojox', 'dijit', 
			'put-selector', 'xstyle', 'dgrid'
		]
	}
});

Criar arquivo em projeto/config/dojo-config.js com o conteúdo (E NÃO em projeto/app/config/dojo-config.js):

window.dojoConfig = {
	async: true
};

Para gerar um servico de map use:

ember g service map

Acesse app/services/map.js e coloque o código exemplo abaixo:

import Ember from 'ember';
import Map from 'esri/Map';
import VectorTileLayer from 'esri/layers/VectorTileLayer';

export default Ember.Service.extend({
	map: null,
	loadMap(){
		let map = this.get('map');
		if(map){
			return map;
		}else{
			let tileLayer = new VectorTileLayer({
				url: "https://www.arcgis.com/apps/Embed/index.html?webmap=432a8d7ca22d4b5b859e0bdaa30ae118&legend=true&details=true"
			});
			map = new Map({
				//layers: [tileLayer]
				basemap: "streets"
			});
			this.set('map', map);
			return map;
		}
	}

});

Crie um componente para usar o mapa:

ember g component esri-map

Inclua o conteúdo:

import Ember from 'ember';
import MapView from 'esri/views/MapView';

export default Ember.Component.extend({
	classNames:['viewDiv'],
	mapService: Ember.inject.service('map'),

	didInsertElement(){
		let map = this.get('map');
		if(!map){
			map = this.get('mapService').loadMap();
			this.set('map',map);
		}
	},

	createMap: function(){
		let map = this.get('map');
		let view = new MapView({
			map:map, 
			container: this.elementId,
			center: [-100.33, 25.69],
			zoom: 10
			});
		view.then(x=> this.set('view', x));
	}.observes('map')
});

Em app/styles/app.css inclua:

@import url('https://js.arcgis.com/4.0/esri/css/main.css');

Em um template inclua o componente criado: {{esri-map}}

Trabalhando com datas

Moment.js para ember-cli [Ember docs](https://guides.emberjs.com/v1.10.0/cookbook/user_interface_and_interaction/displaying_formatted_dates_with_moment_js/ Exemplo Code Pen Bootrap Cli datepicker

ember install ember-moment
import DS from 'ember-data';
import Ember from 'ember';


const {
  computed
} = Ember;

export default DS.Model.extend({
  quandoTermina: DS.attr('date'),
  
  quandoTerminaFormatado: computed('quandoTermina', function () {
    let quandoTermina = this.get('quandoTermina');
    return moment(quandoTermina).format('DD/MM/YYYY');;
  })

})

Para usar o date picker nos templates:

ember install ember-cli-bootstrap-datepicker

No arquivo .ember-cli-build.js inclua importação da tradução do calendário para a língua portuguesa:

var EmberApp = require('ember-cli/lib/broccoli/ember-app');

module.exports = function(defaults) {
  var app = new EmberApp(defaults);
  app.import('bower_components/bootstrap-datepicker/js/locales/bootstrap-datepicker.pt-BR.js');
  return app.toTree();
};

No template:

{{bootstrap-datepicker value=expiresAt language="pt-BR"}}

Link-to nested resources

(StackOverflow)[http://stackoverflow.com/questions/18018502/how-to-link-to-nested-resources-in-ember-js]

Remover da lista de registro um novo registro cuja criação foi cancelada

Em app/tabelas/novo/route.js:

import Ember from 'ember';


const { set } =  Ember;

export default Ember.Route.extend({

	model(){
		let novaTabela = this.store.createRecord('tabela');
		return novaTabela;	
	},
	
	setupController(controller, model){
		set(controller, 'tabela', model);
		set(controller, 'desabilitarEdicaoTabela', false);
	},

	actions:{
		
		criarTabela(tabela){
			tabela.save()
			.then((tabela)=>{
			    this.get('flashMessages').success('Tabela criada com sucesso.');
	       		this.transitionTo('tabelas.restricoes', tabela.id);
			},
			(erro)=>{
			    this.get('flashMessages').danger('Erro: não foi criar a tabela.');
			});
		},

		cancelar(tabela){
			tabela.unloadRecord();
			this.transitionTo('tabelas');
		}
	}
});

No template, verifique se o registro é novo com o atributo isNew como segue:

...
{{#each eixos as |eixo|}}
	{{#if (eq eixo.isNew false) }}
  	<tr>
		<td class="text-center">
	        {{component "btn-detalhes" detalhar=(route-action "detalharEixo" eixo) }}
    	</td>
		<td>{{eixo.siglaSubdivisao}}</td>
		<td>{{eixo.descSubdivisao}}</td>
   </tr>
 {{/if}}
{{/each}}
...

Se o registro não for novo, deve ser listado. Se for novo não deve ser listado porque ao criar um registro o usuário irá para a página de detalhes de registro e não para a listagem. Porém, se o usuário começar a criar o registro e cancelar ou apertar o voltar no navegador, a listagem deverá mostrar apenas os registros 'antigos'.

Enviar id dos objetos para server em todas as requisições

Referência

import DS from 'ember-data';

export default DS.JSONSerializer.extend({
  serialize: function(record, options) {
    options = options || {includeId: true};
    return this._super(record, options);
  }
});

Loop em propriedades de objeto

Referência

{{#each-in objeto as |key value|}}
  {{key}}:{{value}}
{{/each-in}}

Carregar dados adicionais do modelo

Referência

Serializar id como tipo inteiro

Emberjs discussão

import DS from 'ember-data';

export default DS.RESTSerializer.extend({
  serializeBelongsTo: function(record, json, relationship) {
    this._super(record, json, relationship);
    var key = relationship.key;
    var json_key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key;
    json[json_key] = parseInt(json[json_key]);
  },
  serializeHasMany: function(record, json, relationship) {
    this._super(record, json, relationship);
    var key = relationship.key;
    var json_key = this.keyForRelationship ? this.keyForRelationship(key, "hasMany") : key;
    if (json_key in json) {
      json[json_key] = json[json_key].map((item) => {
        parseInt(json[json_key]);
      });
    }
  }
});

Serializar todos os ids como tipo inteiro (Documetação):

import DS from 'ember-data';

export default DS.JSONSerializer.extend({
 serialize: function(snapshot, options) {
    var json = this._super.apply(this, arguments);
   	if(!(typeof json.id === "undefined")){
   		json.id	= parseInt(json.id);
   	} 
    return json;
  }
});

Tratando erros e carregamento lento da página

Em app>pods>application>route.js:

import Ember from 'ember';

export default Ember.Route.extend({	
  ...
   actions: {
    loading(transition, originRoute) {
      let controller = this.controllerFor('application');
      controller.set('carregando', true);
      transition.promise.finally(function() {
          controller.set('carregando', false);
      });
    },
    error(error, transition) {
 	  let controller = this.controllerFor('application');
      if (error) {
      	 if (error.status === 0) {
                error.mensagem ='Desculpe, não foi possível obter os dados do servidor. Este problema pode ser resultado de falta de conexão com a internet. Você pode tentar atualizar a página.';
            } else if (error.status == 403) {
                error.mensagem ='Acesso proibido. Você não possui autorização para acessar esta página.';
            } else if (error.status == 401) {
                error.mensagem ='Acesso proibido. Necessário realizar login.';
            } else if (error.status == 404) {
                error.mensagem ='Página não encontrada.';
            } else {
                error.mensagem = 'Erro ao tentar carregar a página.';
            }
      	controller.set('erro', error);
      }
    },

    voltarParaInicio(){
    	let controller = this.controllerFor('application');
      	controller.set('erro', null);
      	this.transitionTo('index');
    }
  }

});

Crie o controller para application para evitar erro em this.controllerFor('application') na rota:

ember g controller application

Crie os templates para as páginas de carregamento e de erro:

ember g template loading
ember g template error

No template loading, inclua:

<span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span> Carregando...

Pode-se usar o fa fa-spinner também caso esteja usando font-awesome (Referência):

<i class="fa fa-spinner fa-spin" style="font-size:24px"></i>

Em app.css inclua o seguinte para animar o ícone:

.glyphicon-refresh-animate {
    -animation: spin .7s infinite linear;
    -webkit-animation: spin2 .7s infinite linear;
}


@-webkit-keyframes spin2 {
    from { -webkit-transform: rotate(0deg);}
    to { -webkit-transform: rotate(360deg);}
}

@keyframes spin {
    from { transform: scale(1) rotate(0deg);}
    to { transform: scale(1) rotate(360deg);}
}

Em app>pods>application>template.hbs inclua:

...
{{#if carregando}}
    {{partial 'loading'}}
{{else}}
    {{#if erro}}
        {{partial 'error'}}
    {{else}}
        {{outlet}}
    {{/if}}
{{/if}}
...

É necessário adicionar rota para a página de erro em route.js:

this.route('error', {path:'error'});

Carregando um json para a store

store.push(store.normalize('user', { userName: 'myUserName', firstName: 'Dan', lastName: 'LastMe' .....}));

Validação de forms

-Referência

Instalar validator do npm:

npm --save-dev install ember-browserify
npm install --save validator
//app/pods/components/form-book/component.js
import Ember from 'ember';
import DS from 'ember-data';
import Validator from 'npm:validator';

export default Ember.Component.extemd({
	errors: DS.Errors.create(),

	buttonLabel: function(){
		return (this.get('book').id)? 'Salvar':'Criar';
	}.property(),

	actions:{
		submit: function(){
			if(this.validate()){
				this.sendAction('action', this.get('book'));
			}
		}
	}

	validate:function(){
		this.set('errors', DS.Errors.create());
		if( this.get('book.title') === '' || this.get('book.title') === undefined){
			this.get('errors').add('title', 'Não pode ser vazio');

		}

		return this.get('errors.isEmpty');
	}
})
//app/pods/components/form-book/template.hbs
<form {{action 'submit' on='submit'}}>
	
<div class="form-group">
	<label for="">Title</label>
	{{input class="form-control" value=book.title}}
	{{#each errors.title as |error|}}
		<span class="help-block">
			{{error.message}}
		</span>

	{{/each}}
	
</div>
<button class="btn btn-primary" type="submit">{{buttonLabel}}</button>
</form>
//app/pods/book/edit/template.hbs
{{form-book book=book action='updateBook'}}
//app/pods/book/edit/route.js
import Ember from 'ember';

export default Ember.Route.extend({
	model: function(params){
		return this.store.findRecord('book', params.book_id);
	},

	setupController: function(controller, model){
		controller.set('book', model);
	},

	actions: {
		updateBook: function(book){
			var _this = this;
			book.save();
		}
	}
})

Obter propriedades do objeto dinamicamente na view

<form {{action 'submit' on='submit'}}>
	
<div class="form-group {{if (get errors 'siglaSubdivisao') 'has-error'}}">
	<label class="control-label" for="">Sigla Subdivisão: </label>
	{{input class="form-control" value=(mut (get eixo 'siglaSubdivisao')) }}
	{{#each (get errors 'siglaSubdivisao') as |error|}}
		<span class="help-block">
			{{error.message}}
		</span>
	{{/each}}
	
</div>
<button class="btn btn-primary" type="submit">{{buttonLabel}}</button>
</form>

A pesquisar:

  • Addons: liquid-fire
  • Autenticação
  • Templates aninhados. Post>comentários.
  • Uso de ember-cli-mirage
  • uso de ember-web-api add-on para backend em dotnet com pascalcasenames
  • Exemplos de Fixtures
  • Ember-table
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment