Desmistificando um pouco do ember.js

Marcio Junior
4 min readFeb 22, 2015

--

Quando se começa a aprender o Ember.js, um dos primeiros Hello worlds é algo mais ou menos parecido com o que está abaixo:

Até esse ponto se misturam dois sentimentos:

1- Olha que legal, escrevi pouca coisa e consegui exibir um Hello world sem precisar escrever um monte de boilerplate como: instanciar um template, vincular o dado a view, fazer um render etc

2- Que mágica está acontecendo aqui, onde estão os controllers, views? Isso não é um framework MVC? Acredito que ele esteja usando o primeiro template como layout e nesse {{outlet}} é passado o outro template "index", mas como ele sabe que esse layout dever ser usado? E como ele sabe que tem que renderizar o "index"? Provavelmente ele pega o {{model}} e preenche com o retorno do App.IndexRoute#model, mas se eu criar um {{foo}} e retornar "foo" de um novo método chamado App.IndexRoute#foo ele também aparecerá no html? Hmm não deu certo …

Bom esse é um dos problemas com frameworks baseado em convenção sobre configuração, que desanima alguns quando algum erro acontece e não se sabe aonde ele está. Mas vamos quebrar em partes e entender um pouco do que está acontecendo.

Mapeamento de url para rota

No Router (Ember.Router), é onde é mapeado urls para rotas (Ember.Route). Se você usar `this.route("foo")`ele irá considerar que a url /foo está mapeada para `App.FooRoute`.

App.Router.map(function() {  this.route("foo");});App.FooRoute = Ember.Route.extend({});

Você pode alterar a url que mapea determinada rota, por exemplo vamos dizer que você quer que /bar esteja mapeado para `App.FooRoute` para isso vocé usa a opção `path` que é passada como segundo parametro do método `path`:

App.Router.map(function() {  this.route("foo", { path: "bar" });});App.FooRoute = Ember.Route.extend({});

Baseado no que temos acima, o que faria com que ao acessar / na url, seria mapeado para `App.IndexRoute`? Acertou quem disse `this.route('index', { path: "/" })`

App.Router.map(function() {
// se a rota index não for criada, cria o mapeamento abaixo
this.route("index", { path: "/" });});App.IndexRoute = Ember.Route.extend({});

Quando você entra em / numa aplicação, em 99% dos casos você quer exibir algo, então o framework é esperto o suficiente pra criar um mapeamento de "index" para / caso ele não exista.

Ciclo de vida da rota

No tópico anterior vimos como uma url é relacionada com determinada rota, mas em nenhum momento foi dito pra rota: "Renderiza o template com o model", isso acontece porque ao acionar uma determinada rota certos métodos dela são executados, são eles:

Vamos focar mais no model, setupController e renderTemplate.

No método `model` é esperado que seja retornado o modelo de determinada rota (you don’t say). Mas ele oferece uma certa flexibilidade: é possível retornar uma Promise A/+ então não importa muito se o dado é sincrono ou assincrono o framework vai tratar isso:

model: function() {  return localStorage.getItem("users");}

ou

model: function() {  return fetch("/users");}

No `setupController` é onde o model carregado é vinculado ao controller, sua implementação padrão é a seguinte:

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

Lembrando que no ember se usa `obj.set("prop", value)` ao invés de `obj.prop = value` para que o framework saiba que algo mudou e possa atualizar os templates, disparar observers etc.

A variável model passada é aquela que retornamos do método `model`, e se lá foi retornada uma promise, a variável vai ser o objeto pelo qual a promise foi resolvida.

Mas pera aí de onde vem esse controller? Em nenhum momento declaramos ou instanciamos nenhum controller. Se você fizer `controller.toString()` ele vai retornar "(generated index controller)" o que dá a dica do que está acontecendo, sim ele foi gerado automaticamente.

E se eu quiser declarar o meu próprio controller, como faz?

Voltando na declaração da rota, se `this.route("index")` mapea para `App.IndexRoute` para declarar um controller usamos `App.IndexController`, o que é bem intuitivo:

E se eu quiser usar outro controller para essa rota? Por exemplo um `App.BarController`, isso é possível?

Sim, usando a propriedade `controllerName` com o nome do controller desejado:

Até agora falamos dos métodos `model` e `setupController` restando o `renderTemplate` que como você imagina é responsável por renderizar o template

A implementação padrão do `renderTemplate` é a seguinte:

Esse método `render` basicamente diz: "Crie uma view e renderize o template usando as configurações padrões" mas quais são essas configurações padrão?

Já voltaremos nessa pergunta, mas primeiro vamos entender como as views funcionam no ember

Views

Uma view é basicamente uma instancia de `Ember.View` que atua como um "controller" do template, ela usa templates com a sintaxe do Handlebars para mostrar o seu conteúdo. Ex:

O contexto do template, ou seja `{{this}}` é a propriedade `controller` da view que acabamos de criar, por isso ao fazer `{{model}}` é o mesmo que {{controller.model}} ou {{this.model}}, por isso a mensagem "Click here" é exibida. E ao clicar nela um `alert` com a mensagem "clicked" é exibido, já que adicionamos um método `click` na view. Esse exemplo é mais para mostrar como a view se relaciona com o controller e o model, você não deve criar views dessa maneira já que o framework fará isso pra você.

Voltando para o método `render` usado em `renderTemplate` podemos entender que quando escrevi “Crie uma view e renderize o template usando as configurações padrões”, o dito padrões são basicamente criar uma instancia de `Ember.View`, passar como a propriedade `controller` o controller dessa rota (IndexController ou o auto gerado), como propriedade `template` o template dessa rota (data-template-name="index") e adicionar a view no html. É claro que internamente isso tudo é bem mais sofisticado mais é um bom resumo pra entender o que está acontecendo.

Obviamente também da pra alterar esses padrões, por exemplo você quer usar uma view chamada App.FooView na rota App.IndexRoute, basta mudar a propriedade `viewName` para "foo":

Conclusão

Mesmo adotando convenção sobre configuração o ember permite que se use o que você bem entender. Algumas vezes o processo de como as coisas acontecem se tornam um pouco obscuros e entender um pouco como a mágica acontece ajuda na hora que algo não funciona direito. Espero ter ajudado a entender um pouco de como isso acontece. Qualquer dúvida ou sugestão deixem um comentário.

--

--