forked from trek/trek.github.com
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1176 lines (970 loc) · 88.3 KB
/
index.html
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
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Trek by trek</title>
<link rel="stylesheet" href="stylesheets/styles.css">
<link rel="stylesheet" href="stylesheets/pygment_trac.css">
<script src="javascripts/scale.fix.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<div class="wrapper">
<header>
<h1 class="header">Trek</h1>
<p class="header"></p>
<ul>
<li><a class="buttons github" href="https://github.com/trek">GitHub Profile</a></li>
</ul>
</header>
<section>
<h1>Advice on & Instruction in the Use Of Ember.js</h1>
<p><a href="http://emberjs.com/">Ember.js</a> – for the unaware – is an application framework
for building sophisticated browser applications. I'm a frequent contributor to
the project and use it professionally at my current gig with <a href="http://www.groupon.com/techjobs/#/about">Groupon
Engineering</a>. This piece is part
tutorial, part marketing pitch.</p>
<p>There's currently a lot of interest in Ember and its
browser-application-crafting siblings as people are becoming more comfortable
with the browser as a legitimate application development platform and not merely a
ubiquitously deployed rendering engine for server-generated documents. <a href="https://twitter.com/dhh/status/212655990766702594">Not
everyone</a> thinks this pattern
is viable moving forward, but I suspect interest in making this style of
application will only increase over time, become foolishly and inappropriately
overused, before finally settling down as a useful addition to a developer's
toolset.</p>
<p>I'm pretty convinced Ember will be the go-to choice for writing applications
with the sophistication, usefulness, and polish of products like
<a href="http://www.rdio.com/">Rdio</a>, <a href="https://squareup.com/">Square</a>'s web properties,
or <a href="http://get.wunderkit.com/apps/">Wunderkit</a>. Ember is the only framework I've
seen so far that is genuinely tackling the real and difficult requirements of UI
Engineering. I don't say this to knock projects like
<a href="http://batmanjs.org/">Batman</a>, <a href="http://knockoutjs.com/">Knockout</a>,
<a href="http://www.angularjs.org/">Angular</a>, or <a href="http://backbonejs.org/">Backbone</a>.
They're quite good; I've played with all and used Backbone professionally.</p>
<p>Like Ember, they're all experimenting with strategies for connecting data to
display and keeping the two synchronized – a notably difficult task. But only
Ember is approaching this task in a larger scope of UI Engineering that involves
even harder architecture concerns. This is part of what makes Ember.js
challenging for a learner to approach. Have you ever written an app that is
long-running, stateful, requires identity mapping, or must serialize state for
later re-entry? If you're like most web developers I meet, it's highly likely
these are all foreign or novel concepts for you. Even if you've encountered them
before in a CS class or while doing iOS development you've probably never
translated them into the browser environment.</p>
<p>Listen: it's not Ember that's hard. It's the concepts. When people tell me the
learning curve for Angular or Backbone is small, I call bullshit. Small for whom? Sure,
Backbone is approachable if you've spent some time writing applications with jQuery
and are familiar with callback-style evented architectures. Backbone's DNA is
basically <a href="http://css-tricks.com/custom-events-are-pretty-cool/">jQuery custom
events</a> on steroids.
Angular is a dream if you're accustomed to HTML <code>data-</code> style behavior like
you find as part of <a href="http://twitter.github.com/bootstrap/javascript.html">Twitter Bootstrap's javascript</a></p>
<p>Even if Backbone fits squarely into your existing skill set – admittedly true
for most web developers – it's learning curve ramps up steeply if you're
dedicated to writing robust applications. Ever run into <a href="http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/">zombie events in a
Backbone
application</a>?
No? You've either not used it for anything big, have <a href="http://www.imdb.com/title/tt0095953/">Rain
Man</a>-like ability to craft software, or
are fucking shitting me.</p>
<p>Here's an example of some of the view cleanup code in Rdio:</p>
<div class="highlight"><pre><span class="nx">destroy</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">c</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">unbind</span><span class="p">();</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_element</span><span class="p">.</span><span class="nx">pause</span><span class="p">(),</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_element</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s2">"error"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_triggerError</span><span class="p">),</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_element</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s2">"ended"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_triggerEnd</span><span class="p">),</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_element</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s2">"canplay"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_triggerReady</span><span class="p">),</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_element</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s2">"loadedmetadata"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_onLoadedMetadata</span><span class="p">),</span>
<span class="nx">_</span><span class="p">.</span><span class="nx">each</span><span class="p">(</span><span class="nx">b</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">_element</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">c</span><span class="p">.</span><span class="nx">_bubbleProfilingEvent</span><span class="p">)</span>
<span class="p">}),</span>
<span class="nx">_</span><span class="p">.</span><span class="nx">each</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">_element</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">c</span><span class="p">.</span><span class="nx">_logEvent</span><span class="p">)</span>
<span class="p">}),</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_element</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span>
<span class="k">this</span><span class="p">.</span><span class="nx">trigger</span><span class="p">(</span><span class="s2">"destroy"</span><span class="p">)</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">}</span>
</pre></div>
<p>If this doesn't look familiar, you're in for a world of hurt when you try to
parlay your Backbone skills into something Rdio-sized. Backbone is the best solution,
hands down, for apps where the user comes to a page, interacts with the application
for a short time before moving on, letting the <a href="https://github.com/documentcloud/backbone/issues/231">views and the models get thrown
away</a>. Beyond that,
it requires increasing diligence and expertise on your part.</p>
<p>So, here's my pitch: I want you to learn Ember. Not instead of Backbone or
Angular but <em>in addition to</em> them. There's a lot of writing comparing the three,
but once you become familiar with them you'll see it's a totally nonsensical
comparison. Although their output is the same (i.e. a "web app") they just don't
belong in the same category.</p>
<p>I apologize for the long preamble, but we're going to explore some concepts and
I want to be sure you're willing to allow that their strangeness is not due to
the poor architecture of Ember but to your unfamiliarity with them. If you're
willing to learn no matter how funky weird things appear at first, read on. If
you're looking to troll then just skim on: you'll find <em>lots</em> of things to
highlight <a href="https://twitter.com/delambro/status/234997274051219456">when you create a troll twitter account with a single post maligning
a tool you've never bothered to
explore</a>.</p>
<h2>The Smallest Viable Ember Application</h2>
<p>The smallest possible Ember application of interest can be describe thus:</p>
<div class="highlight"><pre><span class="nx">App</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Application</span><span class="p">.</span><span class="nx">create</span><span class="p">();</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">ApplicationView</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">View</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">templateName</span><span class="o">:</span> <span class="s1">'application'</span>
<span class="p">});</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">ApplicationController</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Controller</span><span class="p">.</span><span class="nx">extend</span><span class="p">();</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">Router</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Router</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">root</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">index</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="p">});</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">initialize</span><span class="p">();</span>
</pre></div>
<p>And in our HTML document body or head:</p>
<div class="highlight"><pre><span class="nt"><script </span><span class="na">type=</span><span class="s">"text/x-handlebars"</span> <span class="na">data-template-name=</span><span class="s">"application"</span><span class="nt">></span>
<span class="nt"></script></span>
</pre></div>
<p>Let's examine each piece in isolation.</p>
<div class="highlight"><pre><span class="nx">App</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Application</span><span class="p">.</span><span class="nx">create</span><span class="p">();</span>
</pre></div>
<p>This line creates a new instance of <code>Ember.Application</code>. <code>Application</code> does two handy things:</p>
<ul>
<li>provides a single location for all your objects so we avoid <a href="https://www.google.com/search?q=don't+pollute+the+global+namespace">polluting the global namespace</a>.</li>
<li>creates a single listener for each user event (e.g. 'click') and <a href="https://www.google.com/search?q=event+delegation">controls event delegation</a>.</li>
</ul><p> </p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">ApplicationView</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">View</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">templateName</span><span class="o">:</span> <span class="s1">'application'</span>
<span class="p">});</span>
</pre></div>
<p>Views in Ember are responsible for:</p>
<ul>
<li>determining the structure of a section of the application's rendered HTML.</li>
<li>responding to delegated user events.</li>
</ul><p>In the above view we will change structure of the page only through the view's
template, which will render as the contents of the view's tag, but we could also
provide a different tag name, id, css class, or other HTML attributes for the
rendered element.</p>
<p>Your application <em>must</em> have an <code>ApplicationView</code> property. An instance of this
class will be created for you and inserted into the application's view hierarchy
as the root view.</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">ApplicationController</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Controller</span><span class="p">.</span><span class="nx">extend</span><span class="p">();</span>
</pre></div>
<p>Every view has a rendering context. This is the object where Handlebars
templates will look for properties. So, if your template looks like this:</p>
<pre><code>{{name}}
</code></pre>
<p>and its rendering context has a <code>name</code> property, you'll see the value outputted.
If there is no property, you'll see nothing.</p>
<p>A single instance of <code>ApplicationController</code> will be created for you and
automatically set as the rendering context of the <code>ApplicationView</code>. This is
obvious but bears mentioning: your application <em>must</em> have an
<code>ApplicationController</code> property. If it lacked one, the application's root view
would have no rendering context and would be pretty useless except for
displaying static content. Ember enforces the presence of this property by
throwing an error if it's missing.</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">Router</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Router</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">root</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">index</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="p">});</span>
</pre></div>
<p>A <code>Router</code> in Ember behaves significantly different than you probably suspect
if you have experience with other javascript libraries using the 'router' label.
Ember's <code>Router</code> class is a subclass of its more general purpose <code>StateManager</code>.
Most browser-routers are just copying the routing pattern from familiar server
technologies. But HTTP is specifically a <a href="http://en.wikipedia.org/wiki/Stateless_protocol">stateless
protocol</a> and the techniques
for routing on the server are missing important abilities when translated into
the stateful environment of browser application development.</p>
<p>Your application's router is responsible for tracking the state of your
application and affecting the application's view hierarchy in response to state
change. It is also responsible for serializing this state into a single string –
the URL – and for later deserializing the string into a usable application
state. Rather than being a central organizing technique, URLs are just a useful
side effect of state change.</p>
<p>States are the central feature of an Ember application. Yes, property
observations and automatic view updates are handy, but if that's all Ember
offered it would be only a fraction as useful for serious and robust development.</p>
<div class="highlight"><pre><span class="p">...</span>
<span class="nx">root</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">index</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="p">...</span>
</pre></div>
<p>Your router must have these two routes. The first, <code>root</code>, really just acts as a
container for all subsequent routes. You can think of it as the route set,
rather than a proper route itself. The second <code>index</code>, can be called whatever
you like. The key feature is that it has a <code>route</code> property of <code>'/'</code>. When your
application loads, Ember will being looking through its internal route maps to
find one that matches the url in the browser. If you enter the application at
the url <code>'/'</code> your Router will automatically transition into this state.</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">initialize</span><span class="p">();</span>
</pre></div>
<p>Finally, calling <code>initialize</code> on your application starts the application's
routing process, sets up the necessary internal structure based on configuration
we've done earlier, and inserts an instance of your <code>ApplicationView</code> (with an
instance of <code>ApplicationController</code> as its rending context) into the page.</p>
<h2>Building Up An Application</h2>
<p>From here, we can start building up an application by adding states to our
router, navigable elements in our templates to allow a user to begin
manipulating states, and views that are inserted in response to these state
changes. We'll create a tiny application that lets you see information about
committers to the main Ember repository.</p>
<p>Let's start that process by adding some markup and an <code>outlet</code> into our
currently empty <code>application</code> template:</p>
<pre lang="handlebars"><code><script type="text/x-handlebars" data-template-name="application">
<h1>Ember Committers</h1>
{{outlet}}
</script>
</code></pre>
<p>An <code>outlet</code> helper defines sections of a template where we will change specific
portions of the view hierarchy in response to state change. Any template (not
just the root one) can have any number of outlets (if you give them names). This
lets you express really nuanced view hierarchies with minimal effort.</p>
<p>Next, add a view for all contributors and a matching controller and template:</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">AllContributorsController</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">ArrayController</span><span class="p">.</span><span class="nx">extend</span><span class="p">();</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">AllContributorsView</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">View</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">templateName</span><span class="o">:</span> <span class="s1">'contributors'</span>
<span class="p">});</span>
</pre></div>
<pre lang="handlebars"><code>// in your page body or head:
<script type="text/x-handlebars" data-template-name="contributors">
{{#each person in controller}}
{{person.login}}
{{/each}}
</script>
</code></pre>
<p>Within the state that matches for the index path (<code>'/'</code>), implement a
<code>connectOutlets</code> method. It takes a single argument that will be your
application's router. Within that method get the single instance of our
<code>ApplicationController</code> class and connect its outlet with the <code>connectOutlet</code>
method:</p>
<div class="highlight"><pre><span class="nx">index</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'allContributors'</span><span class="p">,</span> <span class="p">[{</span><span class="nx">login</span><span class="o">:</span><span class="s1">'wycats'</span><span class="p">},{</span><span class="nx">login</span><span class="o">:</span><span class="s1">'tomdale'</span><span class="p">}]);</span>
<span class="p">}</span>
<span class="p">})</span>
</pre></div>
<p>Give your application a reload. You won't see much yet, but this will let you
catch any console errors now.</p>
<p>Let me assuage your obvious fears right now: Yes, this is a lot of code. Yes, it
seems weirdly complex. Yes, you could accomplish this same trivial task in
Backbone or Angular with far less code. Ember isn't targeting applications with
this minimal level of sophistication so it seems foolishly verbose when starting
out.</p>
<p>That said, this is the <em>single</em> central pattern to an application. Once you
master it, you'll be cranking out applications like a pro. Ember applications
start out with a complexity rating of 4/10 but never get much higher than 6/10,
regardless of how sophisticated your application becomes. Backbone starts out at
1/10 but complexity grows linearly. This is a natural side effect of the types
of applications the two frameworks were specifically created for.</p>
<p>Let's unpack our new code, in reverse:</p>
<div class="highlight"><pre><span class="nx">index</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'allContributors'</span><span class="p">,</span> <span class="p">[{</span><span class="nx">login</span><span class="o">:</span><span class="s1">'wycats'</span><span class="p">},{</span><span class="nx">login</span><span class="o">:</span><span class="s1">'tomdale'</span><span class="p">}]);</span>
<span class="p">}</span>
<span class="p">})</span>
</pre></div>
<p>When your application is loaded at the url <code>'/'</code>, Ember will automatically
transition the application into the state I've called <code>index</code>. <code>connectOutlets</code>
is called on this state. It acts as a callback for us to connect sections of our
view hierarchy (designated with <code>{{outlet}}</code>) to specific views based on the
state. In this case I want to connect the <code>{{outlet}}</code> in our application
template with markup for all our contributors so I access the application's
single shared instance of <code>ApplicationController</code> and call <code>connectOutlet</code> on
it with <code>'allContributors'</code> as an argument.</p>
<p>When our application is <code>initialize</code>ed, a single shared instance of each
controller is created for us. Because you'll most likely access this instance
from the router, it's placed as a property of the router with a name that
matches the controller's classname but converted to lower-camel style:
<code>ApplicationController</code>'s single instance is stored as <code>applicationController</code>.</p>
<p>Controllers have the ability to connect outlets in the views they control. In the
above example, I'm calling <code>connectOutlet</code> with <code>'allContributors'</code> as an argument.
This will create an instance of <code>AllContributorsView</code> for us, set the shared instance of
<code>AllContributorsController</code> as the view's default rendering context, and insert
it into our view hierarchy at the point where <code>{{outlet}}</code> appears in the
application template. The second argument, which I've hard coded as an array of
two object literals, is set as the <code>content</code> of the controller instance. (Those
who fear this kind of "magic" are free to read the documentation for Controllers
to see the full, maximally verbose and explicit arguments you can pass).</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">AllContributorsController</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">ArrayController</span><span class="p">.</span><span class="nx">extend</span><span class="p">();</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">AllContributorsView</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">View</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">templateName</span><span class="o">:</span> <span class="s1">'contributors'</span>
<span class="p">});</span>
</pre></div>
<p>The <code>AllContributorsController</code> is a subclass of Ember's <code>ArrayController</code> class.
<code>ArrayController</code>s acts as containers for any array-like object in Ember and
simply proxy undefined properties or methods to the underlying <code>content</code> array.</p>
<p>In our template, the each call (<code>{{each person in controller}}</code>) is passed along
to the <code>content</code> of our ArrayController which I've hard-coded as an array of two
object literals with a single property each.</p>
<pre lang="handlebars"><code><script type="text/x-handlebars" data-template-name="contributors">
{{#each person in controller}}
{{person.login}}
{{/each}}
</script>
</code></pre>
<h2>Loading External Data</h2>
<p>Ember gets a lot of flack for it's lack of "a persistence layer" when compared
to Backbone or Batman. I've never thought of this as fair criticism because I
don't think of thin wrappers around <code>$.ajax()</code> that follow a Rails-style-REST
pattern really merit the "persistence layer" label. And many other frameworks
are starting to realize this too as their thin <code>$.ajax()</code> delegation is being
fleshed out to handle the <a href="https://plus.google.com/u/0/106300407679257154689/posts/Hv6xvZsuBBF">real and difficult problems of reliably synchronizing
data between two environments when there are few structural standards to rely
on</a>.</p>
<p>The real, valid criticism is that nobody who knows Ember has offered much
guidance for how to handle data loading within an Ember application. Ember, as
I'm trying to convince you, has valuable patterns you've never used before and
it's totally unfair to maintain this assertion while simultaneously expecting
you to know how to combine these patterns with data loading solutions you've
previously used.</p>
<p>The best advice I can offer is: always be returning.</p>
<p>Ember relies on the immediate availability of data objects even if the
underlying content of those objects is still loading. This is almost certainly
different than asynchronous patterns for data loading you've used
before. Let's step through how this works, one concern at a time.</p>
<p>In our application so far, I've punted on data and just hard coded something
into our index state:</p>
<div class="highlight"><pre><span class="nx">index</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'allContributors'</span><span class="p">,</span> <span class="p">[{</span><span class="nx">login</span><span class="o">:</span><span class="s1">'wycats'</span><span class="p">},{</span><span class="nx">login</span><span class="o">:</span><span class="s1">'tomdale'</span><span class="p">}]);</span>
<span class="p">}</span>
<span class="p">})</span>
</pre></div>
<p>I'd much prefer to delegate that data loading out to a proper object. Let's call
to a – as yet unimplemented – <code>Contributor</code> class and a <code>find</code> method on that class:</p>
<div class="highlight"><pre><span class="nx">index</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'allContributors'</span><span class="p">,</span> <span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">find</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">})</span>
</pre></div>
<p>And now implement this object:</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nb">Object</span><span class="p">.</span><span class="nx">extend</span><span class="p">();</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">reopenClass</span><span class="p">({</span>
<span class="nx">find</span><span class="o">:</span> <span class="kd">function</span><span class="p">(){}</span>
<span class="p">});</span>
</pre></div>
<p>This creates a new class and reopens that class to add class (sometimes called
'static') method:</p>
<p>If you reload your application you'll see that nothing renders now. This is
because we've set the <code>content</code> of our <code>AllContributorsController</code> to undefined
which is the default return value of our new <code>find</code> method. Let's apply some
<code>$.ajax</code> to the method:</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">reopenClass</span><span class="p">({</span>
<span class="nx">find</span><span class="o">:</span> <span class="kd">function</span><span class="p">(){</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
<span class="nx">url</span><span class="o">:</span> <span class="s1">'https://api.github.com/repos/emberjs/ember.js/contributors'</span><span class="p">,</span>
<span class="nx">dataType</span><span class="o">:</span> <span class="s1">'jsonp'</span><span class="p">,</span>
<span class="nx">success</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">){</span>
<span class="k">return</span> <span class="nx">response</span><span class="p">.</span><span class="nx">data</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="p">});</span>
</pre></div>
<p>Reload your application and you'll see there is still no change because,
although we request data, <code>find</code> still has no return value. It's here that
people usually code themselves into a corner trying to get their previous
experience with ajax to fit into Ember patterns, give up, and post a
StackOverflow question.</p>
<p>There are a few solutions to this problem but the easiest for us now is to just
make sure we're returning an array:</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">reopenClass</span><span class="p">({</span>
<span class="nx">allContributors</span><span class="o">:</span> <span class="p">[],</span>
<span class="nx">find</span><span class="o">:</span> <span class="kd">function</span><span class="p">(){</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
<span class="nx">url</span><span class="o">:</span> <span class="s1">'https://api.github.com/repos/emberjs/ember.js/contributors'</span><span class="p">,</span>
<span class="nx">dataType</span><span class="o">:</span> <span class="s1">'jsonp'</span><span class="p">,</span>
<span class="nx">context</span><span class="o">:</span> <span class="k">this</span><span class="p">,</span>
<span class="nx">success</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">){</span>
<span class="nx">response</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">contributor</span><span class="p">){</span>
<span class="k">this</span><span class="p">.</span><span class="nx">allContributors</span><span class="p">.</span><span class="nx">addObject</span><span class="p">(</span><span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">contributor</span><span class="p">))</span>
<span class="p">},</span> <span class="k">this</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">allContributors</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">});</span>
</pre></div>
<p>I've changed <code>find</code> to immediately return an array (<code>this.allContributors</code>) which
starts out empty. This will become the <code>content</code> of our controller, which is the
default rendering context for the view. When the view first renders it will loop
over the empty array and insert nothing into the page. When the ajax call is
successful we loop through the response from the server turning each chunk
of JSON into an instance of our <code>Contributor</code> class, and add it to the array.
Ember's property notification system will trigger a view re-render for just the
affected sections of the page.</p>
<p>Because Ember has a good property observation system we can handle the
asynchronicity from multiple points within the application structure where it's
most appropriate rather than being forced to handle it at the communication
layer.</p>
<p>If you reload the application you'll see an empty page before the view updates
when the data is loaded. If we were writing a slightly more complex application,
we could use a library by the core team called <a href="https://github.com/emberjs/data">Ember
Data</a> that would help with functionality like
binding loading state to view display. It has far more ambitious goals than
we'll need for demonstration: stateful data synchronization, property encoding
and decoding, identity mapping, transactional communication, and more.</p>
<h2>Transitioning Between States</h2>
<p>With data in hand, we can now allow users to transition between the state where
they see all contributors to a state where they see just one contributor. Our
current router looks like this</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">Router</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Router</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">root</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">index</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'allContributors'</span><span class="p">,</span> <span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">find</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="p">});</span>
</pre></div>
<p>We'll add a sibling state to index for viewing just a single contributor. I'm
also going to rename 'index' to the more descriptive state name of
'contributors':</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">Router</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Router</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">root</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">contributors</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'allContributors'</span><span class="p">,</span> <span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">find</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">}),</span>
<span class="nx">aContributor</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/:githubUserName'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">context</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'oneContributor'</span><span class="p">,</span> <span class="nx">context</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="p">});</span>
</pre></div>
<p>Examining this new state in isolation:</p>
<div class="highlight"><pre><span class="nx">aContributor</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/:githubUserName'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">context</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'oneContributor'</span><span class="p">,</span> <span class="nx">context</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">})</span>
</pre></div>
<p>I've supplied a <code>route</code> property of <code>':/githubUserName'</code>, which we'll use later
to serialize and deserialize this state. I've implemented the <code>connectOutlets</code>
method with two arguments: one to represent the entire router and one, called
<code>context</code>, which will help answer the question "<em>which</em> contributor" later on.
Inside <code>connectOutlets</code> I've accessed the shared instance of
<code>ApplicationController</code> and used it to connect the outlet in its view (an
instance of <code>ApplicationView</code>) to a pairing of
<code>OneContributorView</code>/<code>OneContributorController</code>, which are unimplemented.</p>
<p>Next, we'll update the application template to include a way for users to change
the application's state from 'contributors' to 'aContributor' through
interaction. Currently our template just loops and prints the <code>login</code> property
of each contributor:</p>
<pre lang="handlebars"><code><script type="text/x-handlebars" data-template-name="contributors">
{{#each person in controller}}
{{person.login}}
{{/each}}
</script>
</code></pre>
<p>We're going to encase that login in an <code><a></code> tag that includes a call to the
<code>{{action}}</code> helper:</p>
<pre lang="handlebars"><code><script type="text/x-handlebars" data-template-name="contributors">
{{#each person in controller}}
<a {{action showContributor person}}> {{person.login}} </a>
{{/each}}
</script>
</code></pre>
<p>The <code>{{action}}</code> helper goes <em>within</em> the opening tag of an element (here, the
<code><a></code>) and takes two arguments. The first, <code>showContributor</code>, is the action we'd
like to send to the current state of the application and the second, <code>person</code>,
will become the <code>context</code> argument passed through various callbacks in the
application's router.</p>
<p>If you reload the application now you'll see that our logins have become links.
With your console enabled, click any of the links. You'll see a warning that
your application's router 'could not respond to event showContributor in state
root.contributors'.</p>
<p>Add this transition action to the 'contributors' state. I like to put my actions
between route property definition and route API callbacks like
<code>connectOutlets</code>:</p>
<div class="highlight"><pre><span class="nx">contributors</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span><span class="p">,</span>
<span class="nx">showContributor</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">transitionTo</span><span class="p">(</span><span class="s1">'aContributor'</span><span class="p">),</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'allContributors'</span><span class="p">,</span> <span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">find</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">})</span>
</pre></div>
<p>The new action is written for us by the static method <code>transitionTo</code> on
the <code>Ember.Route</code> class. You can write your transitions yourself (they're just
functions), but <code>Ember.Route.transitionTo</code> saves you trouble of hand-writing
a lot of similar looking functions.</p>
<p>Pop back to the browser, reload the application, and try to transition again.
This time, you'll be warned that we're missing our <code>OneContributorView</code> class.
The transition has occurred and we've reached the <code>connectOutlets</code> callback
on the 'aContributor' state, but cannot properly connect our outlet yet without
the missing view class.</p>
<p>Implement this class and matching controller and template:</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">OneContributorView</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">View</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">templateName</span><span class="o">:</span> <span class="s1">'a-contributor'</span>
<span class="p">});</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">OneContributorController</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">ObjectController</span><span class="p">.</span><span class="nx">extend</span><span class="p">();</span>
</pre></div>
<pre lang="handlebars"><code>// in your HTML document
<script type="text/x-handlebars" data-template-name="a-contributor">
{{login}} - {{contributions}} contributions to Ember.js
</script>
</code></pre>
<p>I've made <code>OneContributorController</code> an instance of <code>Ember.ObjectController</code>.
<code>ObjectController</code> is like <code>ArrayController</code> – a tiny wrapper for objects that will
just proxy property and method access to its underlying <code>content</code> property – but
for single objects instead of collections.</p>
<p>If you reload the application and try to transition you should have more
success. It might be handy to enable logging on your router to get a better feel
for what is happening on transitions:</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">Router</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Router</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">enableLogging</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="c1">// other properties added earlier</span>
<span class="p">})</span>
</pre></div>
<p>Let's unpack what's going on when we click that link. Ember has registered
listener on the <code><a></code> element for you (so, no, this is nothing like going back
to <em>ye olde onclick</em> days) that will call the matching action name
(<code>showContributor</code>) on the <code>target</code> property of the view. It just so happens
that the default target for any view is the application's router.</p>
<p>The router will delegate this action name to the current state. If the action is
present on the state, it will be called with the object you provided as the
second argument to <code>{{action}}</code> as a context argument. If it's not present, the
router will walk up through the state tree towards <code>root</code> looking for a matching
action name.</p>
<p>Since our state <em>does</em> have a matching name,
<code>showContributor: Ember.Route.transitionTo('aContributor')</code>, it's called. This function
transitions the router to the name state ('aContributor') and calls its
<code>connectOutlets</code> callback with the router as the first argument and the context
from the <code>{{action}}</code> helper as the second argument:</p>
<div class="highlight"><pre><span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">context</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'oneContributor'</span><span class="p">,</span> <span class="nx">context</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>Within this method, we access the single shared instance of
<code>ApplicationController</code> and connect the outlet in its view (an instance of
<code>ApplicationView</code>) by inserting an instance of <code>OneContributorView</code> with the
single shared instance of <code>OneContributorController</code> as its default rendering
context.</p>
<p>The <code>content</code> property of this controller is set to the passed <code>context</code>
argument. Since <code>OneContributorController</code> is a descendant of
<code>ObjectController</code>, property access in the view will proxy through the
controller to this <code>content</code>.</p>
<p>The view renders and we see our updated view hierarchy in the browser.</p>
<h2>Serializing and Deserializing States</h2>
<p>Observant readers will notice that, although we supplied a <code>route: '/:githubUserName'</code>
property on our current state, the URL displayed
in the browser has updated to a value of '#/undefined'. I mentioned
earlier that URLs were just a pleasant side effect of state changes
but we haven't talked about serializing and deserializing states yet.</p>
<p>After an application state is entered and <code>connectOutlets</code> has been called, the
router will call <code>serialize</code> on the state with the router itself as the first
argument and the current context as the second argument. There is a default
implementation of <code>serialize</code> that does property lookup on the context using any
dynamic slugs in the supplied <code>route</code> property as keys.</p>
<p>To have serialization work we can either update our <code>route</code> to include dynamic
slugs that match known properties on the object or implement our own custom
method.</p>
<div class="highlight"><pre><span class="nx">aContributor</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/:githubUserName'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">context</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'oneContributor'</span><span class="p">,</span> <span class="nx">context</span><span class="p">);</span>
<span class="p">},</span>
<span class="nx">serialize</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">context</span><span class="p">){</span>
<span class="k">return</span> <span class="p">{</span>
<span class="nx">githubUserName</span><span class="o">:</span> <span class="nx">context</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'login'</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">})</span>
</pre></div>
<p>The return value from a custom <code>serialize</code> method must be an object literal with
keys that match any dynamic slugs in the supplied <code>route</code>. The value for these
keys will be placed in the url.</p>
<p>Browse back to the root state of your application (i.e. go back to '/'),
reload the application, and navigate back to the 'aContributor' state for any
contributor. The url should update properly.</p>
<p>Unfortunately if you reload the application at this particular state you'll see
the URL updates to '#/undefined' again.</p>
<p>When we load an Ember application at a particular url it will attempt to match
and transition into a state with matching <code>route</code> pattern and call the state's
<code>connectOutlets</code> and <code>serialize</code> callbacks. When we reload at '#/kselden', for
example, The router matches to the state with the <code>route</code> pattern of
'/:githubUserName', transitions into it, then calls <code>connectOutlets</code> with the
router as the first argument and a second argument of ... no context at all.
Finally, <code>serialize</code> is called, also with an <code>undefined</code> context, and the
property <code>githubUserName</code> is accessed on <code>undefined</code> and the URL is updated to
'#/undefined'.</p>
<p>Entering the application at a particular URL doesn't give our application access
to previous loaded data so to fully load the matching state, we need to
re-access this data. States have a callback <code>deserialize</code> for doing just this.
There's a default implementation, but we can implement our own custom one as
well:</p>
<div class="highlight"><pre><span class="nx">aContributor</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/:githubUserName'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">route</span><span class="p">,</span> <span class="nx">context</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'oneContributor'</span><span class="p">,</span> <span class="nx">context</span><span class="p">);</span>
<span class="p">},</span>
<span class="nx">serialize</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">context</span><span class="p">){</span>
<span class="k">return</span> <span class="p">{</span><span class="nx">githubUserName</span><span class="o">:</span> <span class="nx">context</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'login'</span><span class="p">)}</span>
<span class="p">},</span>
<span class="nx">deserialize</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">urlParams</span><span class="p">){</span>
<span class="k">return</span> <span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">findOne</span><span class="p">(</span><span class="nx">urlParams</span><span class="p">.</span><span class="nx">githubUserName</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">})</span>
</pre></div>
<p>Above, I've mocked out what I want this deserialization interface to look like.
I'll call <code>App.Contributor.findOne</code> with the section of our url represented by
<code>githubUserName</code> and return this object. The return value of <code>deserialize</code>
becomes the <code>context</code> passed to <code>connectOutlets</code>, so I must immediately return
an object that will get populated with data later. Let's add
<code>App.Contributor.findOne</code> to allow for passing a Github user name. </p>
<p>Github allows us to access a user at '/users/<em>a name</em>', but this isn't within
the context of a particular repository, so we won't have access to this users
contribution count, which is part of the data we need. To see a particular
users in the context of a repository we'll need to load them all and locally
find the one we're looking for. This isn't exactly ideal, but unless you
control development of both client and server it's typical.</p>
<div class="highlight"><pre><span class="nx">findOne</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">username</span><span class="p">){</span>
<span class="kd">var</span> <span class="nx">contributor</span> <span class="o">=</span> <span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">create</span><span class="p">({</span>
<span class="nx">login</span><span class="o">:</span> <span class="nx">username</span>
<span class="p">});</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
<span class="nx">url</span><span class="o">:</span> <span class="s1">'https://api.github.com/repos/emberjs/ember.js/contributors'</span><span class="p">,</span>
<span class="nx">dataType</span><span class="o">:</span> <span class="s1">'jsonp'</span><span class="p">,</span>
<span class="nx">context</span><span class="o">:</span> <span class="nx">contributor</span><span class="p">,</span>
<span class="nx">success</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">){</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setProperties</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">findProperty</span><span class="p">(</span><span class="s1">'login'</span><span class="p">,</span> <span class="nx">username</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="k">return</span> <span class="nx">contributor</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>The order of execution for this method is: create a new <code>Contributor</code> object with
<code>login</code> set immediately to the known value passed in as <code>username</code> from within
the 'aContributor' states's deserialize method. Then we set up some ajax and
immediately return the <code>Contributor</code> instance. When the ajax completes we find
just the contributor we're interested in by looking for the first result with a
a matching username (using <code>findProperty</code>) and update the returned <code>Contributor</code>
instance's properties with <code>setProperties</code>, which will trigger a view update of
any sections bound to properties whose values have changed.</p>
<h2>Repeat</h2>
<p>That's an Ember application. States, transitioning between them, and
loading data when you need it. You can build up surprisingly sophisticated and
robust UIs by repeating this process until you're happy. Let's repeat this by
add a "back to all contributors" navigation to our template for a single contributor:</p>
<p>Right now the template is pretty simple:</p>
<pre lang="handlebars"><code><script type="text/x-handlebars" data-template-name="a-contributor">
{{login}} - {{contributions}} contributions to Ember.js
</script>
</code></pre>
<p>Let's add an element with an action to transition back to the 'contributors'
state:</p>
<pre lang="handlebars"><code><script type="text/x-handlebars" data-template-name="a-contributor">
<div>
<a {{action showAllContributors}}>All Contributors</a>
</div>
{{login}} - {{contributions}} contributions to Ember.js
</script>
</code></pre>
<p>Add this action to the 'aContributor' state:</p>
<div class="highlight"><pre><span class="nx">aContributor</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/:githubUserName'</span><span class="p">,</span>
<span class="nx">showAllContributors</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">transitionTo</span><span class="p">(</span><span class="s1">'contributors'</span><span class="p">)</span>
<span class="c1">// ... remainder of this object's properties</span>
<span class="p">})</span>
</pre></div>
<p>Done.</p>
<p>Nested states are possible and encouraged as well. They come with only one
caveat: you must end the transition between states on state that is a 'leaf'
(i.e. has no child states of its own). As you convert states into more complex
sets of nested states, either remember to directly transition to one of the
child states or set an <code>initialState</code> property.</p>
<p>Let's convert our simple 'aContributor' state into a more complex object with
two child states. The parent 'aContributor' we'll use for fetching a contributor
and displaying her name and number of commits. Then we'll provide two nested
states: one – 'details' – for viewing additional details about the contributor
and a second – 'repos' – for showing a list of their repositories.</p>
<p>For reference, the 'aContributor' state looks like this:</p>
<div class="highlight"><pre><span class="nx">aContributor</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/:githubUserName'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">context</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'oneContributor'</span><span class="p">,</span> <span class="nx">context</span><span class="p">);</span>
<span class="p">},</span>
<span class="nx">serialize</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">context</span><span class="p">){</span>
<span class="k">return</span> <span class="p">{</span><span class="nx">githubUserName</span><span class="o">:</span> <span class="nx">context</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'login'</span><span class="p">)}</span>
<span class="p">},</span>
<span class="nx">deserialize</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">urlParams</span><span class="p">){</span>
<span class="k">return</span> <span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">findOne</span><span class="p">(</span><span class="nx">urlParams</span><span class="p">.</span><span class="nx">githubUserName</span><span class="p">);</span>
<span class="p">},</span>
<span class="p">})</span>
</pre></div>
<p>And we'll change it to this:</p>
<div class="highlight"><pre><span class="nx">aContributor</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/:githubUserName'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">context</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'oneContributor'</span><span class="p">,</span> <span class="nx">context</span><span class="p">);</span>
<span class="p">},</span>
<span class="nx">serialize</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">context</span><span class="p">){</span>
<span class="k">return</span> <span class="p">{</span><span class="nx">githubUserName</span><span class="o">:</span> <span class="nx">context</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'login'</span><span class="p">)}</span>
<span class="p">},</span>
<span class="nx">deserialize</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">,</span> <span class="nx">urlParams</span><span class="p">){</span>
<span class="k">return</span> <span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span><span class="p">.</span><span class="nx">findOne</span><span class="p">(</span><span class="nx">urlParams</span><span class="p">.</span><span class="nx">githubUserName</span><span class="p">);</span>
<span class="p">},</span>
<span class="c1">// child states</span>
<span class="nx">initialState</span><span class="o">:</span> <span class="s1">'details'</span><span class="p">,</span>
<span class="nx">details</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'oneContributorController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'details'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}),</span>
<span class="nx">repos</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/repos'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'oneContributorController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'repos'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">})</span>
</pre></div>
<p>Examining each state in isolation:</p>
<div class="highlight"><pre><span class="nx">initialState</span><span class="o">:</span> <span class="s1">'details'</span><span class="p">,</span>
<span class="nx">details</span><span class="o">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">route</span><span class="o">:</span> <span class="s1">'/'</span><span class="p">,</span>
<span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'oneContributorController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'details'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">})</span>
</pre></div>
<p>When we transition into 'aContributor', its callbacks (<code>connectOutlets</code>,
<code>serialize</code>, optionally <code>deserialize</code> if we're transitioning during application
load) are called. This means the <code>{{outlet}}</code> in our application template is
filled with an instance of <code>OneContributorView</code> with the shared instance of
<code>OneContributorController</code> used as its default rendering context. The <code>context</code>
argument is passed from the <code>{{action showContributor contributor}}</code>, through
the transition, and into this callback. We then pass it along as the second
argument to <code>connectOutlet</code> and it becomes the <code>content</code> property of the shared
<code>OneContributorController</code> instance.</p>
<p>Then, because we have <code>initialState</code> defined the router immediately transitions
into the state 'aContributor.details' and calls its <code>connectOutlets</code> callback:</p>
<div class="highlight"><pre><span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'oneContributorController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'details'</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>In this callback we're connecting an <code>{{outlet}}</code> that we'll place inside the
template for a contributor (yes, outlets can be nested inside other outlets as
deeply as you'd like to). Go ahead and change</p>
<pre lang="handlebars"><code><script type="text/x-handlebars" data-template-name="a-contributor">
<div>
<a {{action showAllContributors}}>All Contributors</a>
</div>
{{login}} - {{contributions}} contributions to Ember.js
</script>
</code></pre>
<p>to</p>
<pre lang="handlebars"><code><script type="text/x-handlebars" data-template-name="a-contributor">
<div>
<a {{action showAllContributors}}>All Contributors</a>
</div>
{{login}} - {{contributions}} contributions to Ember.js
<div>
{{outlet}}
</div>
</script>
</code></pre>
<p>And add <code>DetailsView</code> and template. You can skip creating a <code>DetailsController</code>.
In the absence of a controller with a matching name, Ember will just use the
rendering context of the parent template, which in our case is the shared
instance of <code>OneContributorController</code> with its <code>context</code> property already set
to the contributor we're interested in:</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">DetailsView</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">View</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">templateName</span><span class="o">:</span> <span class="s1">'contributor-details'</span>
<span class="p">})</span>
</pre></div>
<pre lang="handlebars"><code><script type="text/x-handlebars" data-template-name="contributor-details">
<p>{{email}}</p>
<p>{{bio}}</p>
</script>
</code></pre>
<p>Reload the app and navigate to the 'aContributor.details' state by clicking on a
Github username. If you have <code>enableLogging</code> on for your router you'll see we've
successfully transitioned into the state but are missing the <code>email</code> and <code>bio</code>
data. Unfortunately, these properties are not part of the contributor data that
comes from Github. We'll need to trigger a call to Github when we enter this
state to fetch additional details. Let's stub out a call to this in the
<code>connectOutlets</code> for 'aContributor.details':</p>
<div class="highlight"><pre><span class="nx">connectOutlets</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">router</span><span class="p">){</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'oneContributorController.content'</span><span class="p">).</span><span class="nx">loadMoreDetails</span><span class="p">();</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'oneContributorController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s1">'details'</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>And add this method to our <code>Contributor</code> model:</p>
<div class="highlight"><pre><span class="nx">App</span><span class="p">.</span><span class="nx">Contributor</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nb">Object</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">loadMoreDetails</span><span class="o">:</span> <span class="kd">function</span><span class="p">(){</span>