-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy path05-routing.md.erb
333 lines (221 loc) · 18.5 KB
/
05-routing.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
---
title: Rotas
slug: routing
date: 0005/01/01
number: 5
contents: Aprenda sobre rotas no Meteor.|Crie uma página de artigos de discussão, com URLs únicas.|Aprenda como linkar propriamente essas URLs.
paragraphs: 72
---
Agora que temos uma lista de artigos (que eventualmente poderão ser criados pelos utilizadores), precisamos de uma página individual para cada artigo onde os nossos utilizadores poderão discutir cada artigo.
Nós gostaríamos que essas páginas pudessem ser acessível através de um *permalink*, um URL no formato `http://myapp.com/posts/xyz` (onde `xyz` é um identificador `_id` de MongoDB) único para cada artigo.
Isto significa que vamos precisar de algum tipo de *roteamento* para ler o que está na barra de URL do navegador e mostrar o conteúdo correto.
### Adicionando o Pacote Iron Router
[Iron Router](https://github.com/EventedMind/iron-router) é um pacote de roteamento que foi criado especificamente para aplicações Meteor.
Este não só ajuda com roteamento (ou seja, especificar caminhos), como também permite tratar de filtros (associar ações a alguns dos caminhos) e ainda gerir subscrições (controlar que caminho tem acesso a que dados). (Nota: o Iron Router foi desenvolvido em parte pelo co-autor do *Discover Meteor* Tom Coleman.)
Primeiro, vamos instalar o pacote a partir da Atmosphere:
~~~bash
$ mrt add iron-router
~~~
<%= caption "Consola" %>
Este comando baixa e instala o pacote iron-router na nossa aplicação, pronto a usar. Note que por vezes pode ser necessário reiniciar a sua aplicação Meteor (usando `ctrl+c` para matar o processo, e depois `mrt` para o iniciar novamente) antes de o pacote poder ser utilizado.
Note que o Iron Router é um pacote de terceiros, ou seja é necessário ter o Meteorite para o poder instalar (`meteor add iron-router` não vai funcionar).
<% note do %>
### Vocabulário de Roteador
Nós vamos falar de várias características diferentes do roteador neste capítulo. Se tiver alguma experiência com uma framework como Rails, você já estará familiarizado com a maioria desses conceitos. Caso contrário, segue-se um pequeno glossário:
- **Rotas**: Uma rota é o bloco base do roteamento. Básicamente, é um conjunto de instruções que dizem à aplicação onde ir e o que fazer quando esta encontra um URL.
- **Caminhos**: Um caminho é um URL dentro da sua aplicação. Este pode ser estático (`/terms_of_service`) ou dinâmico (`/posts/xyz`), e ainda incluir parâmetros de pesquisa (`/search?keyword=meteor`).
- **Segmentos**: As diferentes partes de um caminho, delimitadas por barras para a frente (`/`).
- **Ganchos**: Ganchos são ações que você gostaria de fazer antes, depois ou até durante o processo de roteamento. Um exemplo típico seria verificar se o utilizador tem os direitos necessários antes de mostrar uma página.
- **Filtros**: Os filtros são simplesmente ganchos que pode definir globalmente para uma ou mais rotas.
- **Templates de Rota**: Cada rota precisa de apontar para um template. Caso não especifique um, por omissão, o roteador vai procurar por um template com o mesmo nome da rota.
- **Layout**: Pode pensar num layout como uma moldura digital de fotos. Eles contêm todo o código HTML que envolve o template atual, e vai permanecer o mesmo quando o template muda.
- **Controlador**: Por vezes, você vai perceber que muitos dos seus templates estão a utilizar os mesmos parâmetros. Em vez de duplicar o seu código, é possível fazer com que todas essas rotas herdem de um único *controlador de roteamento* que irá conter toda a lógica de roteamento.
Para mais informação sobre o Iron Router, consulte [a documentação completa no GitHub](https://github.com/EventedMind/iron-router).
<% end %>
### Roteamento: Mapear URLs para Templates
Até agora, nós construímos o nosso layout utilizando inclusões de templates hard-coded (tais como `{{>postsList}}`). Por isso, apesar de o conteúdo da nossa aplicação poder mudar, a estrutura base da página é sempre a mesma: um cabeçalho, com uma lista de artigos por baixo deste.
O Iron Router permite-nos outra abordagem ao ficar responsável pelo que é renderizado dentro da tag HTML `<body>`. Ou seja, nós não precisamos de definir o conteúdo dessa tag por nós próprios, como faríamos numa página HTML normal. Em vez disso, vamos apontar o roteador para um template layout especial que contem um ajudante de template `{{yield}}`.
O ajudante `{{yield}}` vai definir uma zona dinâmica especial que vai automaticamente renderizar o template correspondente à rota atual (como convenção, vamos designar, a partir de agora, este template especial como o "template da rota"):
<%= diagram "router-diagram", "Layouts e templates.", "pull-center" %>
Vamos começar por criar o nosso layout e adicionar o ajudante `{{yield}}`. Primeiro, vamos remover a nossa tag HTML `<body>` do `main.html`, e mover o seu conteúdo para o seu próprio template, `layout.html`.
O nosso, agora mais magro, `main.html` deve ser agora algo como:
~~~html
<head>
<title>Microscope</title>
</head>
~~~
<%= caption "client/main.html" %>
Enquanto que o recém criado `layout.html` vai agora conter o layout mais exterior da aplicação:
~~~html
<template name="layout">
<div class="container">
<header class="navbar">
<div class="navbar-inner">
<a class="brand" href="/">Microscope</a>
</div>
</header>
<div id="main" class="row-fluid">
{{yield}}
</div>
</div>
</template>
~~~
<%= caption "client/views/application/layout.html" %>
Note que substituímos a inclusão do template `postsList` com uma chamada ao ajudante `yield`. Repare que depois desta alteração, não vemos nada no ecrã. Isto é porque ainda não dissemos ao roteador o que fazer com o URL `/`, e neste caso é mostrado um template vazio.
Para começar, nós podemos recuperar o comportamento antigo mapeando o URL raiz `/` para o template `postsList` . Vamos criar uma diretoria `/lib` na raiz do nosso projecto, e dentro desta criar `router.js` :
~~~js
Router.configure({
layoutTemplate: 'layout'
});
Router.map(function() {
this.route('postsList', {path: '/'});
});
~~~
<%= caption "lib/router.js"%>
Fizemos duas coisas importantes. Primeiro, dissemos ao roteador para usar o layout que acabámos de criar como o layout por omissão para todas as rotas. Segundo, definimos uma nova rota chamada `postsList` e mapeámo-la para o caminho `/`.
<% note do %>
### A diretoria `/lib`
Qualquer coisa que seja colocada dentro da diretoria `/lib` é garantidamente carregado antes que qualquer outra coisa na sua aplicação (com a possível excepção de pacotes inteligentes). Isto faz com que seja um óptimo lugar para código de ajudantes que precisa de estar sempre disponível.
Um aviso: note que como a diretoria `/lib` não está nem dentro de `/client` nem de `/server`, isto significa que os seus conteúdos estarão disponíveis em ambos os ambientes.
<% end %>
### Rotas com Nome
Vamos esclarecer alguma da ambiguidade. Chamámos à nossa rota `postsList`, mas também temos um *template* chamado `postsList`. O que é que se está aqui a passar?
Por omissão, o Iron Roter vai procurar por um template com o mesmo nome da rota. Na realidade, ele vai até procurar por um *caminho* baseado no nome da rota, ou seja, se não tivéssemos definido um caminho personalizado (que fizemos ao providenciar uma opção `path` na nossa definição de roteador), o nosso template não estaria acessível por omissão no URL `postsList`.
Outra dúvida possível é porque é que precisamos sequer de dar um note às nossas rotas. Dar nomes a rotas permite-nos utilizar algumas características do Iron Router que tornam mais fácil criar links dentro da nossa aplicação. O mais útil é o ajudante Handlebars `{{pathFor}}`, que devolve o caminho URL de qualquer rota.
Queremos que o nosso link principal de casa aponte para a lista de artigos, por isso em vez de especificar um URL estático `/`, nos podemos também utilizar o ajudante Handlebars. O resultado final será o mesmo, mas esta abordagem dá-nos mais flexibilidade dado que o ajudante irá sempre fazer output do URL correto mesmo que o caminho no roteador seja alterado.
~~~html
<header class="navbar">
<div class="navbar-inner">
<a class="brand" href="{{pathFor 'postsList'}}">Microscope</a>
</div>
</header>
//...
~~~
<%= caption "client/views/application/layout.html"%>
<%= highlight "3" %>
<%= commit "5-1", "Roteamento muito básico." %>
### Esperando por Dados
Caso publique a versão atual da aplicação (ou lance uma instância utilizando o link acima), irá notar que a lista aparece vazia por uns momentos antes dos artigos aparecerem. Isto é porque quando a página primeiro carrega, não existem artigos para mostrar enquanto a subscrição dos `posts` não carrega os dados dos artigos do servidor.
Seria muito melhor para a experiência de utilização se pudéssemos disponibilizar algum feedback visual que algo está a acontecer, e que o utilizar deve esperar alguns momentos.
Por sorte, o Iron Router tem uma forma fácil de fazer isso -- vamos `waitOn` (esperar pela) subscrição:
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() { return Meteor.subscribe('posts'); }
});
Router.map(function() {
this.route('postsList', {path: '/'});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "3,4" %>
Vamos por partes. Primeiro, modificámos o bloco `Router.configure()` para dar ao roteador o nome do template de carregando (que vamos criar a seguir) para onde se deve redirecionar enquanto esperamos por dados.
Segunda, também adicionamos uma função `waitOn`, que devolve a nossa subscrição `posts`. O que isto significa é que o roteador vai garantir que a subscrição `posts` está carregada antes de mandar o utilizador pela rota que ele pediu.
Note que como estamos a definir a nossa função de `waitOn` globalmente ao nível da rota, esta sequência apenas vai acontecer uma vez quando o utilizador acede à sua aplicação pela primeira vez. Depois disso, os dados vão estar na memória do navegador e a rota não vai precisar de esperar por eles novamente.
E como nós estamos a deixar o roteador gerir a nossa subscrição, você pode agora removê-la com segurança do `main.js` (que deve agora estar vazio).
Normalmente é uma boa ideia esperar pelas suas subscrições, não apenas pela experiência de utilização, mas também porque isto significa que se pode assumir com segurança que os dados vão estar disponíveis nos templates. Isto elimina a necessidade de lidar com templates serem renderizados antes de os dados subjacentes estarem disponíveis, coisa que normalmente requer abordagens não ideais.
A peça final do puzzle é o template de carregamento. Vamos usar o pacote `spin` para criar um template de carregamento animado engraçado. Este pode ser adicionado com o comando `mrt add spin`, e depois crie o template `loading` da seguinte forma:
~~~html
<template name="loading">
{{>spinner}}
</template>
~~~
<%= caption "client/views/includes/loading.html" %>
Note que `{{>spinner}}` é um template parcial contido no pacote `spin`. Apesar de este template parcial vir de "fora" da nossa aplicação, nós podemos utilizá-lo como qualquer outro template.
<%= commit "5-2", "Esperar na subscrição do artigo." %>
<% note do %>
### Um Primeiro Olhar Sobre A Reatividade
Reatividade é um conceito chave de Meteor, e apesar de ainda não termos realmente abordado o tópico, o nosso template de carregamento dá-nos um primeiro olhar sobre este conceito.
Redirecionar para um template de carregamento se os dados ainda não foram carregados está muito bem, mas como é que o roteador sabe quando deve redirecionar o utilizador *de volta* para a página correta assim que os dados foram carregados?
Por agora, vamos apenas dizer que isto é exatamente onde a reatividade entra, e ficar por aqui. Mas não se preocupe, vai aprender mais sobre o assunto brevemente!
<% end %>
### Roteando Para Um Post Específico
Agora que vimos como rotear para o template `postsList`, vamos criar uma rota para mostrar os detalhes de um único artigo.
Existe apenas um problema: nós não podemos prosseguir e definir uma rota por artigo, já que pode haver centenas deles. Neste caso é necessário definir uma única rota *dinâmica*, e fazer essa rota mostrar qualquer artigo que queiramos.
Para começar, vamos criar um novo template que simplesmente renderiza o mesmo template de artigo que utilizámos anteriormente na lista de artigos.
~~~html
<template name="postPage">
{{> postItem}}
</template>
~~~
<%= caption "client/views/posts/post_page.html" %>
Posteriormente vamos adicionar mais elementos a este template (como por exemplo comentários), mas por agora este vai servir simplesmente como um contentor para a nossa inclusão `{{> postItem}}`.
Vamos criar outra rota com nome, desta vez mapeando caminhos URL na forma `/posts/<ID>` para o template `postPage`:
~~~js
Router.map(function() {
this.route('postsList', {path: '/'});
this.route('postPage', {
path: '/posts/:_id'
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "4~6" %>
A sintaxe especial `:_id` indica ao roteador duas coisas: primeiro, para combinar qualquer rota na forma `/posts/xyz/`, onde "xyz" pode ser qualquer coisa. Segundo, para por seja o que for que encontrar neste "xyz" dentro de uma propriedade `_id` na lista `params` do roteador.
Note que estamos apenas a usar `_id` por uma questão de conveniencia. O roteador não tem forma de saber se você lhe está a passar realmente um `_id`, ou uma string aleatória de caracteres.
Estamos agora a rotear para o template correto, mas ainda nos falta alguma coisa: o roteador sabe o `_id` do artigo que queremos mostrar, mas o template não faz ideia de qual é. Então, como é que damos a volta a este problema?
Felizmente, o roteador tem uma solução inteligente: este deixa especificar o **contexto de dados** do template. Pode-se pensar no contexto de dados como o recheio dentro de um bolo delicioso feito de templates e layouts. De forma simples, é o que se usa para encher o template:
<%= diagram "router-diagram-2", "O contexto de dados.", "pull-center" %>
No nosso caso, podemos ir buscar o contexto de dados adequado procurando pelo artigo usando o `_id` que obtivemos do URL:
~~~js
Router.map(function() {
this.route('postsList', {path: '/'});
this.route('postPage', {
path: '/posts/:_id',
data: function() { return Posts.findOne(this.params._id); }
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "4~7" %>
Assim, cada vez que o utilizador acede a esta rota, ele vai encontrar o artigo correspondente e vai passá-lo ao template. Lembre-se que `findOne` devolve um único artigo que corresponde a uma pesquisa, e que providenciar apenas um `_id` como argumento é um atalho para `{_id: id}`.
Dentro da função `data` (dados em português) para uma rota, `this` corresponde à rota atual, e podemos utilizar `this.params` para aceder às partes com nome da rota (partes que indicámos prefixando-as com `:` dentro da nossa `path`).
<% note do %>
### Mais Sobre Contexto de Dados
Ao definir o *contexto de dados* de um template, pode controlar o valor de `this` dentro de um ajudante de template.
Isto é normalmente feito implicitamente com o iterador `{{#each}}`, que automaticamente define o contexo de dados de cada iteração para o item atual da interação:
~~~html
{{#each widgets}}
{{> widgetItem}}
{{/each}}
~~~
Mas também o podemos fazer explicitamente utilizando `{{#with}}`, que simplesmente diz "toma este objecto, e aplica-lhe este template". Por exemplo, podemos escrever:
~~~html
{{#with myWidget}}
{{> widgetPage}}
{{/with}}
~~~
Também é possível obter o mesmo efeito passando o contexto como um *argumento* da chamada ao template. Ou seja, o bloco anterior de código pode ser reescrito como:
~~~js
{{> widgetPage myWidget}}
~~~
<% end %>
### Utilizando Um Ajudante de Roteador Com Nome Dinâmico
Por fim, temos de ter a certeza que estamos a apontar para o lugar certo quando queremos fazer um link para um artigo individual. Novamente, podemos usar algo como `<a href="/posts/{{_id}}">`, mas usar um ajudante de roteador é mais seguro.
Chamámos a rota do artigo `postPage`, por isso podemos usar um ajudante `{{pathFor 'postPage'}}`:
~~~html
<template name="postItem">
<div class="post">
<div class="post-content">
<h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
</div>
<a href="{{pathFor 'postPage'}}" class="discuss btn">Discuss</a>
</div>
</template>
~~~
<%= caption "client/views/posts/post_item.html"%>
<%= highlight "6" %>
<%= commit "5-3", "Rotear para uma única página de artigo." %>
Mas espera, como exatamente é que o roteador sabe onde ir buscar a parte do `xyz` em `/posts/xyz`? No final de contas, não lhe estamos a passar nenhum `_id`.
Na realidade, o Iron Router é suficientemente inteligente para perceber isso por si próprio. Nós estamos a dizer ao roteador para usar a rota `postPage`, e o roteador sabe que esta rota precisa de um `_id` de alguma espécie (dado que foi assim que definimos a nossa `path`) .
Assim, o roteador vai procurar por este `_id` no local mais lógico disponível: o contexto de dados do ajudante `{{pathFor 'postPage'}}`, ou notras palavras `this`. E acontece que o nosso `this` corresponde a um artigo, que (surpresa!) tem uma propriedade `_id`.
Alternativamente, também é possível dizer explicitamente ao roteador onde é que ele deve procurar pela propriedade `_id`, passando um segundo argumento ao ajudante (ou seja, `{{pathFor 'postPage' someOtherPost}}`). Um possível cenário onde este padrão poderá ser usado é ao construir o link para o artigo anterior ou seguinte numa lista.
Para verificar se funciona corretamente, navegue para a lista de artigos e clique num dos links `Discuss`. Deve ver algo como:
<%= screenshot "5-2", "Uma única página de artigo." %>
<% note do %>
### HTML5 pushState
Uma coisa a perceber é que estas mudanças de URL estão a ser feitas utilizando [HTML5 pushState](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history?redirectlocale=en-US&redirectslug=Web%2FGuide%2FDOM%2FManipulating_the_browser_history).
O Roteador apanha cliques em URLs que são internos ao site, e evita que o navegador navegue para fora da aplicação, e em vez disso faz as alterações necessárias ao estado da aplicação.
Se tudo funcionar corretamente, a página deve mudar de forma instantânea. De facto, às vezes as coisas mudam tão rápido que pode ser necessária uma transição de página. Isto está fora do ambito deste capítulo, mas não deixa de ser um tópico interessante.
<% end %>