-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy path12-pagination.md.erb
470 lines (343 loc) · 22 KB
/
12-pagination.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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
---
title: Dzielenie dokumentu na strony
slug: pagination
date: 0012/01/01
number: 12
contents: Dowiedz się wiecej o subskrypcjach w Meteorze i wykorzystaniu ich do kontroli danych.|Zaimplementuj nieskończone dzielenie dokumentu na strony.|Użyj pakietu `iron-router-progress` aby zaimplementować pasek postępu w stylu iOS.|Utwórz specjalną subskrypcję dla bezpośrednich linków do strony z postem.
paragraphs: 67
---
Sprawy z Microscope mają się świetnie i możemy oczekiwać, że aplikacja stanie się hitem po opublikowaniu jej na świat.
Z tego względu powinniśmy pomyśleć o wpływie jaką ma liczba postów renderowanych na stronie na wydajność serwisu po starcie!
Wcześniej rozmawialiśmy o tym, jak kolekcja po stronie klienta powinna zawierać podzbiór danych obecny na serwerze i nawet udało się nam to osiągnąć dla kolekcji zawierających powiadomienia i komentarze.
Obecnie jednak nadal publikujemy wszystkie posty za jednym zamachem, do wszystkich połączonych użytkowników. Aby rozwiązać ten problem, potrzebujemy podzielić nasze posty na strony.
### Dodawanie postów
Po pierwsze, w danych początkowych, załadujmy wystarczającą liczbę postów, tak aby dzielenie na strony miało sens.
~~~js
// Dane początkowe
if (Posts.find().count() === 0) {
//...
Posts.insert({
title: 'The Meteor Book',
userId: tom._id,
author: tom.profile.name,
url: 'http://themeteorbook.com',
submitted: now - 12 * 3600 * 1000,
commentsCount: 0
});
for (var i = 0; i < 10; i++) {
Posts.insert({
title: 'Test post #' + i,
author: sacha.profile.name,
userId: sacha._id,
url: 'http://google.com/?q=test-' + i,
submitted: now - i * 3600 * 1000,
commentsCount: 0
});
}
}
~~~
<%= caption "server/fixtures.js" %>
<%= highlight "15~24" %>
Po wywołaniu komendy `meteor reset` powinieneś zobaczyć coś podobnego do:
<%= screenshot "12-1", "Wyświetlanie wymyślonych danych . " %>
<%= commit "12-1", "Dodane wystarczająco postów aby potrzebne było dzielenie dokumentu na strony." %>
### Nieskończone dzielenie dokumentu na strony.
Zaimplementujemy proste "nieskończone" dzielenie dokumentu na strony. Oznacza to, że wyświetlimy, przyjmijmy 10 postów na ekranie z linkem âpokaż więcejâ umieszczonym pod spodem. Kliknięcie na ten link doda kolejne 10 postów do listy i tak dalej *w nieskończoność*. Oznacza to, że możemy kontrolować nasz cały system dzielenia na strony przez pojedyńczy parametr zawierający liczbę postów do wyświetlenia na ekranie.
Teraz będziemy musieli znaleźć sposób na przekazanie serwerowi o tym parametrze, tak aby wiedział ile postów wysłać klientowi. Tak się składa, że subskrybujemy już publikację `posts` w routerze, więc wykorzystamy to i pozwolimy routerowi na kontrolę nad dzieleniem na strony.
Najłatwiejszym sposobem na ustawienie tego jest po prostu umieszczenie parametru oznaczającego granicę postów w ścieżce dającej URL w formie `http://localhost:3000/25`. Dodatkowym bonusem używania URL w porównaniu do innych metod jest to, że jeżeli wyświetlasz obecnie 25 postów i odświeżysz stronę przez pomyłkę, nadal będziesz widział 25 postów na stronie po przeładowaniu strony.
Aby zrobić to dobrze, musimy zmienić sposób, w jaki subskrybujemy na posty. Tak, jak poprzednio w rozdziale *Komentarze*, będziemy musieli przenieść nasz kod odpowiedzialny za subskrypcje z poziomu *routera* na poziom *ścieżki routera*.
Możliwe, że wyda się to być zbyt dużą liczbą nowych informacji do przyswojenia, ale stanie się to zrozumiałe po przeczytaniu kodu.
Po pierwsze, przestaniemy subskrybować do publikacji `posts` w bloku `Router.configure()`. Po prostu usuń `Meteor.subscribe('posts')` pozostawiając jedynie subskrypcję `notifications`:
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() {
return [Meteor.subscribe('notifications')]
}
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "5" %>
Dodamy następnie parametr `postsLimit` to ścieżki routingu. Dodanie `?` po nazwie parametru oznacza, że jest opcjonalny. Tak więc ścieżka nie tylko będzie pasowała do `http://localhost:3000/50`, ale również do zwykłego `http://localhost:3000`.
~~~js
Router.map(function() {
//...
this.route('postsList', {
path: '/:postsLimit?'
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "5" %>
Istotne jest zanotowanie, że ścieżka w formie `/:parametr?` będzie pasowała do każdej możliwej ścieżki. Ponieważ każda trasa będzie parsowana kolejno i sprawdzana czy pasuje do bieżącej ścieżki musimy się upewnić, że ścieżki są ułożone w kolejności od najbardziej specyficznej do najbardziej ogólnej.
Inaczej mówiąc, ścieżki routingu, które mają być dopasowane do najbardziej szczegółowych tras takich jak `/posts/:_id` powinny być pierwsze, a ścieżka `postsList` powinna być przesunięta na ostatnią pozycję, ponieważ pasuje w sumie do wszystkiego.
Teraz czas na zmierzenie się z ciężkim problem subskrypcji i znajdowania właściwych danych. Musimy obsłużyć przypadek, gdzie parametr `postsLimit` nie jest obecny, więc przypiszemy mu wartość domyślną. Użyjemy â5â aby zapewnić dostatecznie dużo miejsca na dzielenie na strony.
~~~js
Router.map(function() {
//..
this.route('postsList', {
path: '/:postsLimit?',
waitOn: function() {
var postsLimit = parseInt(this.params.postsLimit) || 5;
return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: postsLimit});
}
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "6~9" %>
Zauważysz, że przkazujemy teraz obiekt JavaScript ({limit: postsLimit}) jak i nazwę publikacji `posts`. Ten obiekt będzie służył jako parametr `opcje` dla `Posts.find()` po stronie serwera. Przejdźmy zatem do kodu wykonywanego po stronie serwera i zaimplementujmy:
~~~js
Meteor.publish('posts', function(options) {
return Posts.find({}, options);
});
Meteor.publish('comments', function(postId) {
return Comments.find({postId: postId});
});
Meteor.publish('notifications', function() {
return Notifications.find({userId: this.userId});
});
~~~
<%= caption "server/publications.js" %>
<%= highlight "1~3" %>
<% note do %>
### Przekazywanie parametrów
Nasz kod publikacji w rzeczywistości przekazuje serwerowi, że może zaufać dowolnemu obiektowi JavaScript wysyłanego przez klienta (w naszym przypadku `{limit: postsLimit}`) który służy jako `opcje` dla polecenia `find()`. Umożliwia to użytkownikom przesłanie wybranych opcji przez konsolę przeglądarki.
W naszym przypadku, jest to stosunkowo nieszkodliwe, ponieważ wszystko, co użytkownik mógłby zrobić, to zmienić kolejność postów lub zmienić liczbę wyświetlanych (co chcemy zrobić na początek).
Nie powinno się jednak używać tego wzorca podczas zapisywania prywatnych danych na niepublikowanych polach, ponieważ użytkownik odpowiednio modyfikując opcję `fields` mógłby mieć do nich dostęp i powinieneś również unikać używania go dla argumentu selektora `find()` dla tych samych powodów bezpieczeństwa.
Bardziej bezpieczne byłoby przekazywanie pojedyńczych parametrów zamiast całego obiektu, aby upewnić się że masz pełną kontrolę nad dostępem do danych:
~~~js
Meteor.publish('posts', function(sort, limit) {
return Posts.find({}, {sort: sort, limit: limit});
});
~~~
<% end %>
Teraz kiedy subskrybujemy posty na poziomie konkretnej ścieżki URL, miałoby sens ustawić kontekst danych w tym samym miejscu. Odejdźmy trochę od poprzedniego schematu i zmieńmy funkcję `data` w ten sposób, aby zwracała obiekt JavaScript zamiast zwracania kursora. To pozwala na utworzenia *nazwanego* kontekstu danych, który nazwiemy `posts`.
Oznacza to, że zamiast pośredniego dostępu jako `this` w środku szabklonu, nasz kontekst danych będzie dostępny jako `posts`. Oprócz tej małej zmiany, kod powinien wyglądać znajomo:
~~~js
Router.map(function() {
this.route('postsList', {
path: '/:postsLimit?',
waitOn: function() {
var limit = parseInt(this.params.postsLimit) || 5;
return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: limit});
},
data: function() {
var limit = parseInt(this.params.postsLimit) || 5;
return {
posts: Posts.find({}, {sort: {submitted: -1}, limit: limit})
};
}
});
//..
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "8~13" %>
Teraz skoro ustawiamy kontekst danych na poziomie routera możemy bezpiecznie pozbyć się nagłówka szablonu `posts` w pliku `posts_lists.js`. Skoro nazwaliśmy kontekst danych `posts` (tak samo jak helper), nie podrzebujemy nawet dotykać szablonu `postList`!
Zbierzmy wszystko razem. Oto jak powinien wyglądać nowy i poprawiony kod `router.js`:
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() {
return [Meteor.subscribe('notifications')]
}
});
Router.map(function() {
//...
this.route('postsList', {
path: '/:postsLimit?',
waitOn: function() {
var limit = parseInt(this.params.postsLimit) || 5;
return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: limit});
},
data: function() {
var limit = parseInt(this.params.postsLimit) || 5;
return {
posts: Posts.find({}, {sort: {submitted: -1}, limit: limit})
};
}
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "5, 11~21" %>
<%= commit "12-2", "Zmieniona trasa postsList pozwalająca na ograniczenie liczby postów." %>
Wypróbujmy nowy system dzielenia postów. Mamy teraz możliwość wyświetlania dowolnej liczby postów na stronie głównej za pomocą zmiany parametru URL. Przykładowo, sþróbuj odwiedzić `http://localhost:3000/3`. Powinieneś ujrzeć coś podobnego do:
<%= screenshot "12-2", "Kontrola liczby postów na stronie głównej. " %>
<% note do %>
### Dlaczego nie osobne strony?
Dlaczego używamy *nieskończonego dzielenia na strony* zamiast pokazywać kolejne strony zawierające po 10 postów każda, jak Google pokazujące wynik zapytania? Robimy to ze względu na reakcje Meteora w czasie rzeczywistym.
Wyobraźmy sobie, że dzielimy naszą kolekcję `Posts` używając systemu używanego przez Google i jesteśmy na stronie 2, która pokazuje posty od 10 do 20. Co dzieje się, gdy użytkownik usunie nagle pierwsze 10 postów?
Ponieważ nasza aplikacja działa w czasie rzeczywistym, nasz zbiór danych uległby zmianie. Post 10 stałby się postem 9 i wypadł z widoku, a post 11 byłby widoczny, Końcowy rezultat byłby taki, że użytkownik zobaczyłby nagle zmieniające się posty bez widocznej przyczyny!
Nawet jeżeli tolerowalibyśmy tą niedogodność UX, tradycyjne dzielenie dokumentu jest również trudne do zrealizowania z przyczyn technicznych.
Wróćmy do poprzedniego przykładu. Publikujemy posty od 10 do 20 z kolekcji `Posts`, ale jak znaleźć te posty po stronie klienta? Nie możesz wybrać postów 10 do 20, skoro po stronie klienta jest tylko 10 postów.
Jednym rozwiązaniem byłoby publikowanie 10 postów po stronie serwera i następnie wywoływanie `Posts.find()` po stronie klienta, aby przechwycić *wszystkie* publikowane posty.
Działa to, gdy masz tylko jedną subskrypcję. Ale co się stanie jeżeli będziesz miał więcej niż jedną subskrypcję postów, a stanie się to wrótce?
Powiedzmy, że jedna subskrypcja pyta o posty od 10 do 20, a inna o 30 do 40. Masz teraz 20 postów załadowane po stronie klenta, nie mając pojęcia które należą do odpowiedniej subskrypcji.
Ze względu na te wszytkie kwestie, tradycyjne dzielenie na dokumetu na strony nie ma większego sensu przy korzystaniu z Meteora.
<% end %>
### Implementacja kontrolera Routera
Mogłeś zauważyć, że powtórzyliśmy dwukrotnie linię `var limit = parseInt(this.params.postsLimit) || 5;`. Dodatkowo, numer zakodowany numer “5†nie jest idealny. To nie koniec świata, ale ponieważ zawsze warto stosować się do zasady DRY (Don't Repeat Yourself), sprawdźmy jak możemy zrobić refaktoryzację kodu.
Wprowadźmy nową funkcję Iron Routera, *Kontrolerow ścieżki*. Kontroler ścieżki umożliwia na zebranie razem funkcjonalności w jeden spójny pakiet, z którego może dziedziczyć jakakolwiek ścieżka. Teraz użyjemy kontrolera do pojedyńczej ścieżki, a w następnym rozdziale dowiesz się jak pomocna będzie ta funkcjonalność.
~~~js
PostsListController = RouteController.extend({
template: 'postsList',
increment: 5,
limit: function() {
return parseInt(this.params.postsLimit) || this.increment;
},
findOptions: function() {
return {sort: {submitted: -1}, limit: this.limit()};
},
waitOn: function() {
return Meteor.subscribe('posts', this.findOptions());
},
data: function() {
return {posts: Posts.find({}, this.findOptions())};
}
});
Router.map(function() {
//...
this.route('postsList', {
path: '/:postsLimit?',
controller: PostsListController
});
});
~~~
<%= caption "lib/router.js" %>
Przejdźmy przez ten krok. Początkowo tworzymy kontroler przez rozszerzenie `RouteController`'a. Następnie używamy właściwości `Template` jak poprzednio, a następnie nowej właściwości `increment`.
Następnie definiujemy nową funkcję `linit`, która zwróci obecny limit i funkcję `findOptions` która zwróci obiekt z opcjami. Może się wydawać dodatkowym niepotrzebnym krokiem, ale zrobimy z niego później użytek.
Następnie, zdefiniujemy nowe funkcje `waitOn` i `data` jak poprzednio, przy czym używają one obecnie funkcji `findOptions`.
Ostatnią rzeczą do zrobienia jest zmiana ścieżki `postsList` prowadzącą do naszego nowego kontrolera z własnością `controller`.
<%= commit "12-3", "Zrefaktoryzowana ścieżka postsLists do RouteController'a'." %>
### Dodawanie linku 'Pokaż więcej'
Mamy teraz działające dzielenie dokumentu na strony i kod wygląda dobrze. Jest tylko jeden problem: nie ma innego sposobu na *używanie* tego dzielenia na strony niż ręczna zmiana URL. Zdecydowanie nie polepsza to używania go, więc weźmy się do pracym aby go polepszyć.
To, co chcemy osiągnąć jest wystarczająco proste. Dodamy przycisk *wczytaj więcej* na dole listy postów, co zwiększy o 5 liczbę postów wyświetlanych po każdym kliknięciu. Więc jeżeli jesteśmy na URL `http://localhost:3000/5`, kliknięcie na *wczytaj więcej* powinno przenieść na `http://localhost:3000/10`. Jeżeli zdołałeś przeczytać do te pory tą książkę, powinieneś dać sobie radę z prostą arytmetyką!
Tak, jak poprzednio, dodamy logikę dzielenia na strony do naszej ścieżki. Pamietasz, gdy jawnie nazwaliśmy kontekst danych zamiast używać anonimowego kursora? Nie ma zasady, która mówiłaby, że funkcja `data` może przekazywać jedynie kursory, więc użyjemy tej samej techniki aby wygenerować przycisk *wczytaj więcej*.
~~~js
PostsListController = RouteController.extend({
template: 'postsList',
increment: 5,
limit: function() {
return parseInt(this.params.postsLimit) || this.increment;
},
findOptions: function() {
return {sort: {submitted: -1}, limit: this.limit()};
},
waitOn: function() {
return Meteor.subscribe('posts', this.findOptions());
},
posts: function() {
return Posts.find({}, this.findOptions());
},
data: function() {
var hasMore = this.posts().fetch().length === this.limit();
var nextPath = this.route.path({postsLimit: this.limit() + this.increment});
return {
posts: this.posts(),
nextPath: hasMore ? nextPath : null
};
}
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "16~23" %>
Przyjrzyjmy się głębiej na trick wykorzystywany przez routera. Pamiętaj, że ścieżka `postList` (która dziedziczy z kontrolera `PostListController` nad którym obecnie pracujemy, bierze parametr `postLimit`.
Z tego względu, gdy wprowadzamy `{postsLimit: this.limit() + this.increment}` do `this.route.path()`, nakazujemy ścieżce `postsList` zbudować własną ścieżkę używając powyższego obiektu JavaScript jako kontekstu danych.
Innymi słowy, jest to dokładnie to samo, co użycie helpera Handlebars `{{pathFor 'postsList'}}`, z tym, że zamieniamy pośrednio dostępne `this` przez własny stworzony kontekst danych.
Wykorzystujemy tą ścieżkę i dodajemy ją do kontekstu danych dla naszego szablonu, ale *wyłącznie* wtedy, gdy jest więcej postów do wyświetlenia. Sposób, w jaki to robimy, jest trochę podchwytliwy.
Wiemy, e `this.limit()` zwraca bieżącą liczbę postów, którą chcielibyśmy pokazać, która może być zawarta w bieżącym URL, lub jest wartością domyślną (5), jeżeli URL nie zawiera żadnego parametru.
Z drugiej strony, `this.posts()` odnosi się do bieżącego kursora, zatem `this.posts.fetch().length` odnosi się do liczby postów znajdujących się w obrębie danego kursora.
Zatem, jeżeli pytamy o `n` postów i dostajemy `n`, będziemy pokazywali przycisk *wczytaj więcej*. Jeżeli zapytamy o `n` i dostaniemy *mniej* niż `n`, oznacza to, że osiągneliśmy limit i nie powinniśmy wyświetlać tego przycisku.
Niestety nie ma prostych rozwiązań tego problemu, z tego względu zostaniemy na razie z tą niezbyt perfekcyjną implementacją.
Wszystko, co pozostało do zrobienia, to dodanie linka *wczytaj więcej* pod listą postów, upewniając się, żeby pokazać go gdy jest więcej postów dostępnych do wczytania.
~~~html
<template name="postsList">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
{{#if nextPath}}
<a class="load-more" href="{{nextPath}}">Load more</a>
{{/if}}
</div>
</template>
~~~
<%= caption "client/views/posts/posts_list.html" %>
<%= highlight "7~10" %>
Oto jak powinna wyglądać teraz lista postów:
<%= screenshot "12-3", "Przycisk “wczytaj więcej”. " %>
<%= commit "12-4", "Dodany nextPath() do kontrolera i użycie go do przechodzenia przez posty." %>
<% note do %>
### Liczba postów a ich długość
Możesz się zastanawiać, dlaczego użyliśmy `this.posts.fetch().length` zamiast `this.posts.count()`. Jest to tymczasowe obejście buga (https://github.com/meteor/meteor/issues/654) obecnego w Metorze podczas pisania tej książki. Miejmy nadzieję, że zostanie poprawiony szybko!
<% end %>
### Lepszy pasek postępu
Dzielenie liczby postów na strony działa teraz prawidłowo, ale ma wadę: za każdym razem po wciśnięciu przycisku `wczytaj więcej` router wysyła zapytanie do bazy o posty, zostajemy przekierowani do szablony `loading` podczas oczekiwania na nowe dane. W wyniku tego zostajemy odesłani na samą górę strony i musimy przesuwać ją za każdym razem z powrotem.
Byłoby o wiele lepiej, gdybyśmy mogli zostać na tej samej stronie podczas wykonywania całej operacji i wyświetlali wskaźnik wczytywania danych. Dokładnie do tego służy pakiet `iron-router-progress`.
Podobnie to Safari działającej w iOS lub stron takich jak Medium czy YouTube, `iron-router-progress` dodaje cienki pasek postępu na górze strony. Do zaimplementowanie tego wystarczy dodanie pakietu do Twojej aplikacji:
~~~bash
mrt add iron-router-progress
~~~
<%= caption "bash console" %>
Dzięki pracy wykonywanej przez smart packages, nasz wskaźnik wygląda od razu perfekcyjnie! Będzie aktywowany dla każdej ścieżki i automatycznie zakończony gdy dane skończą się ładować.
Zrobimy tylko jedną zmianę. Wyłączymy pakiet `iron-router-progress` dla ścieżki `postSubmit`, ponieważ nie musi ona czekać na żadne dane (jest to po prostu pusty formularz):
~~~js
Router.map(function() {
//...
this.route('postSubmit', {
path: '/submit',
disableProgress: true
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "7" %>
<%= commit "12-5", "Użycie pakietu iron-router-progress aby polepszyć widok przy dzieleniu postów na strony." %>
### Dostęp do dowolnego postu
Obecnie domyślnie wczytujemy pięć najnowszych postów. Co stanie się, jeżeli ktoś przejdzie bezpośrednio do strony posta?
<%= screenshot "12-4", "Pusty szablon." %>
Jeżeli to sprawdzisz, zobaczysz pusty szablon. To ma sens: nakazaliśmy routerowi subskrypcję do publikacji `posts` podczas wczytywania ścieżki `postsList`, ale nie wskazaliśmy żadnych instrukcji dla ścieżki `postPage`.
Wszystko, co poznaliśmy do tej pory, to sposób subskrypcji listy `n` najnowszych postów. Jak zapytać serwer o konkretny post? Poznasz teraz mały sekret: można mieć więcej niż jedną publikację każdej kolekcji!
Aby zatem odzyskać nasze zagubione posty, utworzymy nową, oddzielną publikację `singlePost`, która publikuje tylko jeden post znaleziony po `_id`.
~~~js
Meteor.publish('posts', function(options) {
return Posts.find({}, options);
});
Meteor.publish('singlePost', function(id) {
return id && Posts.find(id);
});
~~~
<%= caption "server/publications.js" %>
<%= highlight "5~7" %>
Zasubskrybujemy teraz odpowiednie posty po stronie klienta. Subskrybujemy już publikację `comments` w funkcji `waitOn` ścieżki `postPage`, zatem możemy w prosty sposób dodać w tym samym miejscu subskrypcję do ścieżki `postEdit`, ponieważ wymaga ona tych samych danych.
~~~js
Router.map(function() {
//...
this.route('postPage', {
path: '/posts/:_id',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('comments', this.params._id)
];
},
data: function() { return Posts.findOne(this.params._id); }
});
this.route('postEdit', {
path: '/posts/:_id/edit',
waitOn: function() {
return Meteor.subscribe('singlePost', this.params._id);
},
data: function() { return Posts.findOne(this.params._id); }
});
/...
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "7~12,18~20" %>
<%= commit "12-6","Użycie pojedyńczej subskrypcji posta, aby upewnić siœ, żee widzimy wlaściwy post." %>
Mając ukończoną implementację dzielenia postów na strony, nasza aplikacja pozbyła się problemów związanych ze skalowaniem. Użytkownicy są pewni dodawać większą liczby linków, niż poprzednio. Czy nie było by dobrze znaleźć sposób na ustalenie rankingu linków? Jest to dokładnie temat następnego rozdziału *Głosowanie*.