-
Notifications
You must be signed in to change notification settings - Fork 11
/
12-pagination.md.erb
689 lines (548 loc) · 29.4 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
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
---
title: ページネーション
slug: pagination
date: 0012/01/01
number: 12
points: 10
photoUrl: http://www.flickr.com/photos/ikewinski/8625379401/
photoAuthor: Mike Lewinski
contents: Meteorのサブスクリプションを使ったデータの操作方法を学びます。|無限に続くスタイルのページネーションを実装します|`iron-router-progress`パッケージでiOSスタイルのプログレスバーを作ります。|投稿ページへの直接リンクを扱う特別なサブスクリプションを作成します。
paragraphs: 67
version: 1.7.1
---
見た目がすごいMicroscopeであれば、それが世界にリリースされたとき、大ヒットを期待することができます。
ですので、実際にリリースする前に、
新しい投稿の数によるパフォーマンスの意義について少し考える必要があります!
これまでに、どのようにクライアントサイドのコレクションが サーバーのデータの一部を含めるのか説明しました。
また、notificationとcommentsコレクションでも同じことを行いました。
しかし今のところ、まだ私たちは接続しているユーザーに対して、1回のアクセスでパブリッシュしています。
リンクが数千掲載されてた場合、最終的には、これは問題になります。
これを解決するために、我々は我々の記事をページ分割する必要があります。
### もっとたくさんの投稿を追加します
最初に、ページネーションが実際に意味をなすように、`fixture.js`にデータに十分な投稿を詰め込みましょう。:
~~~js
// Fixture data
if (Posts.find().count() === 0) {
//...
Posts.insert({
title: 'The Meteor Book',
userId: tom._id,
author: tom.profile.name,
url: 'http://themeteorbook.com',
submitted: new Date(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: new Date(now - i * 3600 * 1000),
commentsCount: 0
});
}
}
~~~
<%= caption "server/fixtures.js" %>
<%= highlight "15~24" %>
`meteor reset`を実行した後で、アプリを再起動すると、こんな感じになります:
<%= screenshot "12-1", "Displaying dummy data. " %>
<%= commit "12-1", "Added enough posts that pagination is necessary." %>
### 無限ページネーション
私たちは"無限"スタイルのページネーションを実装しています。
この意味というのは、最初にスクリーンに10個の投稿を表示し、
下部に“load more”リンクを配置するということです。
このリンクをクリックすると、さらに10個の投稿をリストに追加し、*際限なく*続きます。
つまり、画面上に表示する投稿の数を表す単一のパラメータを使用して、
全体のページネーションシステムを制御できることを意味します。
この単一パラメータがサーバーにどれだけの数の投稿をクライアントに送信するか識別できるように指示する方法が必要となります。
それは、すでにルータの`posts`パブリケーションにサブスクライブしているので、
これを利用して、ルータに同様にページネーションを処理してもらいます。
これを設定する最も簡単な方法は、フォームに単純にパスのポストリミットパラメータ部分を設定した`http://localhost:3000/25`のようなURLを与えることです。
他の方法に比べて、URLを使用することの追加ボーナスは、
現在25の記事を表示し、誤ってブラウザウィンドウをリロードしてしまっている場合、あなたはまだ、
再びページが読み込また時に、同じ25の投稿を見ることができる点です。
これを正しく行うために、私たちは投稿にサブスクリプションする方法を変える必要があります。
以前に*コメントの作成*の章で行ったように、
私たちはサブスクリプションのコードを*ルーター*レベルから*ルート*レベルに移行する必要があります。
これは、すべてを一度に取り込むことがたくさんかもしれないが、コードは綺麗になるはずです。
最初に、私たちは`Router.configure()`ブロック内で`posts`パブリケーションへのサブスクリプションを停止します。
といっても、`Meteor.subscribe('posts')`を削除するだけで、`notifications`サブスクリプションだけを残します。
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() {
return [Meteor.subscribe('notifications')]
}
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "6" %>
それから`postsLimit`引数をルートのパスに追加します。引数の名前の後に`?`を追加することは、オプショナルであることを意味しています。そのため、ルートは`http://localhost:3000/50`にマッチするだけでなく、元の古い`http://localhost:3000`にもマッチします。
~~~js
//...
Router.route('/:postsLimit?', {
name: 'postsList',
});
//...
~~~
<%= caption "lib/router.js" %>
<%= highlight "3" %>
`/:parameter?`形式のパスがすべての可能なパスにマッチしているか着目することは重要です。
各ルートはそれがカレントパスにマッチしているかどうか見るためにうまくパースされるので、特異性を減少させるために、私たちのルートを整理し確認する必要があります。
言い換えると、`/posts/:_id`のような、より特殊なルートをターゲットするルートは、最初に来るべきで、
`postsList`ルートはファイルの**下層に**移すべきです。というのは、すべてにちゃんとマッチさせるためです。
これからサブスクライブして正しいデータを見つける上で、大変な問題に取り掛かることなります。
私たちは`postsLimit`引数が存在しない場合に対処する必要があるので、
私たちは デフォルトの値をそれに割り当てます。
私たちはページネーションをいじくるための十分な余地をあたえるために、“5”を使います。
~~~js
//...
Router.route('/:postsLimit?', {
name: 'postsList',
waitOn: function() {
var limit = parseInt(this.params.postsLimit) || 5;
return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: limit});
}
});
//...
~~~
<%= caption "lib/router.js" %>
<%= highlight "5~8" %>
`posts`パブリケーションの名前と一緒に、私たちが
JavaScript オブジェクト`({limit: postsLimit})`を引き渡していることにお気づきでしょう。
このオブジェクトは サーバーサイドの`Posts.find()`文への`オプション`引数として使われます。
このように実装して、サーバーサイドのコードに切り替えてみましょう。
~~~js
Meteor.publish('posts', function(options) {
check(options, {
sort: Object,
limit: Number
});
return Posts.find({}, options);
});
Meteor.publish('comments', function(postId) {
check(postId, String);
return Comments.find({postId: postId});
});
Meteor.publish('notifications', function() {
return Notifications.find({userId: this.userId});
});
~~~
<%= caption "server/publications.js" %>
<%= highlight "1~7" %>
<% note do %>
### パラメータを渡す
パブリケーションコードは`find()`文のオプションとして渡す、
サーバーにクライアントから送られたどのようなJavaScriptオブジェクト
(この場合は{limit: postsLimit})でも信頼するように指示を出しています。
これでブラウザーコンソールでユーザーが好きなそんなオプションも送信することができるようになります。
この場合、これは比較的無害です。
というのは、一人のユーザーがするすべてのことは違う投稿に再指示することだったり、
(最初に許可したいなど)したい制限を変えることだからです。
実際のアプリはおそらく限界を制限する必要があるでしょう!
ありがたいことに、`check()`を使用することにより、我々は、ユーザーが
(いくつかのケースでは`fields`のような書類上のプライベートデータを公開したいというような)
こっそり追加のオプションを設定することはできないことを知っています。
より安全なパターンは、あなたのデータの制御にとどまることを確認するために、
個々のパラメータ自身の代わりに、オブジェクト全体を渡すことかもしれないです。:
~~~js
Meteor.publish('posts', function(sort, limit) {
return Posts.find({}, {sort: sort, limit: limit});
});
~~~
<% end %>
今、私たちは、ルートレベルでサブスクライブしており、それはまた、同じ場所でのデータコンテキストを設定するために理にかなっています。
既存パターンから少し外れますが `data`関数は、単にカーソルを返す代わりにJavaScriptオブジェクトを返すようにします。
これは、私たちが`posts`と呼ぶことにした*名付けられた*のデータコンテキストを、作成しています。
Now that we're subscribing at the route level, it would also make sense to set the data context in the same place. We'll deviate a bit from our previous pattern and make the `data` function return a JavaScript object instead of simply returning a cursor. This lets us create a *named* data context, which we'll call `posts`.
これが意味することは、その代わり、暗黙的にテンプレート内部の`this`は、
データコンテキストとして`posts`で利用できるようになるということです。
これとは別に小さな要素から、コードが身近に感じなければならない:
What this means is simply that instead of being implicitly available as `this` inside the template, our data context will be available at `posts`. Apart from this small element, the code should feel familiar:
~~~js
//...
Router.route('/:postsLimit?', {
name: 'postsList',
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 "9~14" %>
ルートレベルでデータコンテキストをセットしたので、
`posts_list.js`ファイル内の`posts`テンプレートヘルパーを安全に取り除くことができます。
私たちはデータコンテキストをヘルパーと同じ名前の`posts`と名づけたので、
私たちは`postsList`テンプレートを触る必要がありません!
それではおさらいしてみましょう。ここに私たちの新しい改良`router.js`コードがどのように見えるかです:
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() {
return [Meteor.subscribe('notifications')]
}
});
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return Meteor.subscribe('comments', this.params._id);
},
data: function() { return Posts.findOne(this.params._id); }
});
Router.route('/posts/:_id/edit', {
name: 'postEdit',
data: function() { return Posts.findOne(this.params._id); }
});
Router.route('/submit', {name: 'postSubmit'});
Router.route('/:postsLimit?', {
name: 'postsList',
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})
};
}
});
var requireLogin = function() {
if (! Meteor.user()) {
if (Meteor.loggingIn()) {
this.render(this.loadingTemplate);
} else {
this.render('accessDenied');
}
} else {
this.next();
}
}
Router.onBeforeAction('dataNotFound', {only: 'postPage'});
Router.onBeforeAction(requireLogin, {only: 'postSubmit'});
~~~
<%= caption "lib/router.js" %>
<%= highlight "6,25~37" %>
<%= commit "12-2", "Augmented the postsList route to take a limit." %>
私たちのブランドの新しいページネーションシステムを試してみましょう。
今、単純にURLパラメータを変更することで、
ホームページ上の記事を任意の数を表示する機能を持っています。
たとえば、`http://localhost:3000/3`にアクセスしてみてください。
あなたは今、このようなものが表示されるはずです。:
<%= screenshot "12-2", "Controlling the number of posts on the homepage. " %>
<% note do %>
### なんでページじゃない?
なぜ私たちはGoogleの検索結果ページのように
10個の投稿ごとに 次のページを表示するのではなく、
“無限ページネーション”アプローチを使っているのでしょうか?。
それは、Meteorによるリアルタイムパラダイムだからです。
Googleの検索結果ページネーションパターンを使って、`Posts`コレクションをページングすることを想像しましょう。
そして、10番目から20番目の投稿を表示する2ページ目にいるとします。
もし他のユーザーが1ページ目の10個の投稿のどれかを削除したとしたら、どうなるのでしょうか?
このアプリはリアルタイムなので、データセットが変化します。
10番目の投稿は今、9番目になり、画面から消え去ります。
11番目の投稿が表示対象となります。
最終的に、ユーザーは目に見えない理由で急に投稿の変化を見ることになります!
たとえ私たちが このUXの特異な行動を我慢したとしても、
従来のページネーションは技術的な理由で実行することが難しいです。
先ほどの例に戻りましょう。
私たちは10から20の投稿を`Posts`コレクションからパブリッシュしています。
しかし、クライアントでのこうした投稿を、あなたはどのようにして見つけるのでしょうか?
クライアントサイドのデータセットの中に全体で10個の投稿しかないので、
あなたは10から20の投稿を取得することはできません。
1つの解決策はサーバーで10個の投稿をパブリッシュすることで、
そのときにパブリッシュされた*すべて*の投稿を取り出すためにクライアントサイドで`Posts.find()`を行います。
もしあなたが1つだけのサブスクリプションをもつとしたら、
これはうまくいきます。
すぐにできます。しかし、もし1つ以上の投稿サブスクリプションを持つするとしたら、どうなるのでしょうか?
それではひとつ目のサブスクリプションが10〜20番目のポストを要求し、
別の一つが30〜40番目の投稿を要求したとしましょう。
合計でクライアント側のロードされた20個の記事を持っていますが、どのサブスクリプションから得たものか
知る方法はありません。
以上のような理由から、Meteorと連携している時に従来のページネーションはあまり意味をなさないのです。
<% end %>
### ルートコントローラーの作成
`var limit = parseInt(this.params.postsLimit) || 5;`の行を2度繰り返していることにお気づきかもしれません。
さらに、数字の “5” を ハードコーディングすることは 理想的ではありません。
これはそれほど深刻なことではありませんが、
できればDRY (Don't Repeat Yourself) 原則に従うのがよいので、
どのようにリファクタリングするのか少し見ていきましょう。
ここで Iron Router の新しい機能、*ルートコントローラ*を紹介します。
ルートコントローラはルーティング機能をどんなルートでも引き継いで、
再利用可能で素晴らしいパッケージにまとめるためのシンプルな方法です。
ここでは1つのルートにルートコントローラを使いますが、
次の章ではこの機能がいかに役立つのか見ていきます。
~~~js
//...
PostsListController = RouteController.extend({
template: 'postsList',
increment: 5,
postsLimit: function() {
return parseInt(this.params.postsLimit) || this.increment;
},
findOptions: function() {
return {sort: {submitted: -1}, limit: this.postsLimit()};
},
waitOn: function() {
return Meteor.subscribe('posts', this.findOptions());
},
data: function() {
return {posts: Posts.find({}, this.findOptions())};
}
});
//...
Router.route('/:postsLimit?', {
name: 'postsList'
});
//...
~~~
<%= caption "lib/router.js" %>
<%= highlight "3~18, 25" %>
一つ一つ見ていきましょう。最初に、`RouteController`を拡張してコントローラを作ります。
それから以前行ったように、`template`プロパティをセットして、
その次に新たに`increment`プロパティをセットします。
新たに現在の上限を返す`limit`関数と
オプションオブジェクトを返す`findOptions`関数を定義します。
これは余分なステップのように見えるかもしれませんが、後々これを利用します。
次に以前と同じように`waitOn`関数と`data`関数を定義します。
ただし、新しく利用している`findOptions`関数は除きます。
コントローラは`PostsListController`と呼ばれ、ルートが`postsList`と命名されているので、
Iron Routerは自動的にコントローラを使用します。
ですので(コントローラは、ここでそれらを処理しているため)
ルート定義から`waitOn`と`data`を削除する必要があります。
私たちは別の名前でコントローラを使用するために必要な場合、
`controller`オプションはを使用できます。(次の章でその一例が表示されます)
<%= commit "12-3", "Refactored postsLists route into a RouteController." %>
### "More Link"のロードを追加する
ページネーションは動作しているので、コードは良さそうに見えます。
1つだけ問題があります:
ページネーションを実際に使うには、URL を手動で変えるしかありません。
これではユーザーエクスペリエンスに全く役立ちません。
そのため、この修正に取り掛かりましょう。
私たちがやりたいことはシンプルです。
投稿リストの下に “load more” ボタンを追加して
クリックされるごとに、
現在表示されている投稿の数値を5増加させます。
つまり、私が現在`http://localhost:3000/5`にいるとしたら、
“load more”ボタンを押すと、私は`http://localhost:3000/10`に移動します。
ここまで読み進めた方だったら、このちょっとした算数ができるはずです!
すでに述べたように、ルートにページネーションロジックを追加します。
私たちは、明示的に匿名カーソルを使用するのではなく、
命名したデータコンテキストを使ったことを覚えていますか?
まあ、そこに`data`関数はカーソルのみを渡すことができないと言うルールはありませんので、
“load more”ボタンのURLを生成するために、同じ方法を使用します。
~~~js
//...
PostsListController = RouteController.extend({
template: 'postsList',
increment: 5,
postsLimit: function() {
return parseInt(this.params.postsLimit) || this.increment;
},
findOptions: function() {
return {sort: {submitted: -1}, limit: this.postsLimit()};
},
waitOn: function() {
return Meteor.subscribe('posts', this.findOptions());
},
posts: function() {
return Posts.find({}, this.findOptions());
},
data: function() {
var hasMore = this.posts().count() === this.postsLimit();
var nextPath = this.route.path({postsLimit: this.postsLimit() + this.increment});
return {
posts: this.posts(),
nextPath: hasMore ? nextPath : null
};
}
});
//...
~~~
<%= caption "lib/router.js" %>
<%= highlight "15~25" %>
このルーターマジックについて、さらに深く見ていきましょう。
私たちが現在取り組んでいる`PostsListController`コントローラーから引き継がれた
`postsList`ルートが`postsLimit`引数をとることを思い出しましょう。
`{postsLimit: this.limit() + this.increment}`を`this.route.path()`に入力した時、
私たちはデータコンテキストとしてJavaScript オブジェクトを使って、
`postsList`ルートに自身のパスを作るように命令しています。
言い換えると、自身のカスタムメイドのデータコンテキストによって、
暗黙的な`this`を取り替えることを除いて、
これはまさに`{{pathFor 'postsList'}}`Spacebarsヘルパーを使うことと同じことです。
表示するより多くの記事がある場合に*のみ*、
そのパスを取って、私たちのテンプレートのデータコンテキストにそれを追加している。
それを行う方法は少しトリッキーです。
`this.limit()`は、私たちが表示したい現在の投稿数を返します。
または、現在のURLを基にした値か、URLにパラメータが含まれていなければ、デフォルト値(5)を返します。
他方で、`this.posts`は現在のカーソルを参照するので、
`this.posts.count()`は実際にカーソルの中の投稿数を参照します。
ここで私たちが言っていることは、もし私たちが`n`posts を要求して`n`を戻すと、
“load more”ボタンを表示し続けるということです。
リミットに達すると`n`*より小さい*の値が返るので、私たちはそのボタンの表示をストップすべきです。
とはいえ、このシステムはあるケースにて失敗します:
もし、データベースのアイテム数がは*正確に*`n`出会った場合、何が起こるかというと、
クライアントが`n`投稿あることを確認し取得すると“load more”ボタンが表示し続けるのです。
残念なことに、この問題に単純な次善策はないので、
今のところ私たちはこの完全でない実装を解決する必要があります。
もしさらに投稿をロードさせるとしたら、
するために残されていることは投稿リストのボタンの下に“load more”リンクを追加して、このリンクだけをを表示します。
~~~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/templates/posts/posts_list.html" %>
<%= highlight "7~10" %>
これで投稿リストは現在このようになっているはずです:
<%= screenshot "12-3", "The “load more” button. " %>
<%= commit "12-4", "Added nextPath() to the controller and use it to step through posts." %>
### より良いユーザー体験
ページネーションは現在正常に動作していますが、変な癖があります:
毎回私たちは“load more”をクリックして、ルータは、新しいデータが入ってくるようにするために、
我々は待っている間Iron Routerの`waitOn`機能は`loading`テンプレートに私たちを送信し、
より多くのポストを要求します。
その結果、私たちは、送信さるたびにページの上部に戻され、
私たちのブラウジングを再開する度に下にスクロールする必要があるということです。
だから最初、私たちはすべての後にサブスクリプションを`waitOn`しないように
Iron Router に指示する必要があります。
その代わりに、我々は、`subscriptions`フックで私たちのサブスクリプションを定義します。
また、データコンテキストの一部として`this.postsSub.ready`を参照できる`ready`変数を渡している。
これで、ポストスクリプションがロードを完了したことを、
テンプレートに教えてあげます。
~~~js
//...
PostsListController = RouteController.extend({
template: 'postsList',
increment: 5,
postsLimit: function() {
return parseInt(this.params.postsLimit) || this.increment;
},
findOptions: function() {
return {sort: {submitted: -1}, limit: this.postsLimit()};
},
subscriptions: function() {
this.postsSub = Meteor.subscribe('posts', this.findOptions());
},
posts: function() {
return Posts.find({}, this.findOptions());
},
data: function() {
var hasMore = this.posts().count() === this.postsLimit();
var nextPath = this.route.path({postsLimit: this.postsLimit() + this.increment});
return {
posts: this.posts(),
ready: this.postsSub.ready,
nextPath: hasMore ? nextPath : null
};
}
});
//...
~~~
<%= caption "lib/router.js" %>
<%= highlight "12~14, 23" %>
ポストの新しいセットをロードしている間、ポストリストの下部にあるスピナーを表示するために、
テンプレートにこの`ready`変数をチェックします:
~~~html
<template name="postsList">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
{{#if nextPath}}
<a class="load-more" href="{{nextPath}}">Load more</a>
{{else}}
{{#unless ready}}
{{> spinner}}
{{/unless}}
{{/if}}
</div>
</template>
~~~
<%= caption "client/templates/posts/posts_list.html" %>
<%= highlight "10~12" %>
<%= commit "12-5", "Add a spinner to make pagination nicer." %>
### Accessing Any Post
私たちは現在、デフォルトで最新の5つの投稿をロードしていますが、
投稿の個別ページを見たい人がいたらどうなるでしょうか?
<%= screenshot "12-4", "An empty template." %>
これを試すと、“not found”エラーに出くわします。
これはこういうことです:
`postsList`ルートをロードする時に、
私たちはルーターが`posts`パブリケーションにサブスクライブするように指示を出しています。
しかし、私たちは`postPage`ルートに何をするのか指示を出しませんでした。
しかし、今までで私たちが 行う方法のすべては`n`の最新の投稿リストにサブスクライブすることです。
では、サーバーに1つの特定の投稿を要求するにはどうしたら良いのでしょうか?
ここでちょっとした秘密を教えましょう:
あなたは 各コレクションに対して1つ以上のパブリケーションを持つことができます!
行方不明の投稿を取り戻すために、新たに独立した`singlePost`パブリケーションを作ります。
これは`_id`で識別して1つの投稿だけをパブリッシュします。
~~~js
Meteor.publish('posts', function(options) {
return Posts.find({}, options);
});
Meteor.publish('singlePost', function(id) {
check(id, String)
return Posts.find(id);
});
//...
~~~
<%= caption "server/publications.js" %>
<%= highlight "5~7" %>
さあ、クライアントサイドに正しい投稿をサブスクライブしましょう。
私たちはすでに`postPage`ルートの`waitOn`関数に`comments`パブリケーションをサブスクライブしています。
そのため、ここでで`singlePost`にサブスクリプションを簡単に追加できます。
`postEdit`ルートへのサブスクリプション追加することを忘れずに。
というのは、`postEdit`ルートも同じデータが必要だからです。
~~~js
//...
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('comments', this.params._id)
];
},
data: function() { return Posts.findOne(this.params._id); }
});
Router.route('/posts/:_id/edit', {
name: 'postEdit',
waitOn: function() {
return Meteor.subscribe('singlePost', this.params._id);
},
data: function() { return Posts.findOne(this.params._id); }
});
//...
~~~
<%= caption "lib/router.js" %>
<%= highlight "6~9,16~18" %>
<%= commit "12-6","Use a single post subscription to ensure that we can always see the right post." %>
ページネーションが完了すると、このアプリはスケーリングの問題に苦しむことはありません。
そのため、ユーザーは以前よりも多くのリンクを投稿することでしょう。
では、こうしたリンクにどうにかしてランク付けする良い方法はないものでしょうか?
これこそが、次の章の話題です!