/
atom.xml
297 lines (267 loc) · 22.8 KB
/
atom.xml
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[Category: dry | machty's thoughtz]]></title>
<link href="http://machty.github.com/blog/categories/dry/atom.xml" rel="self"/>
<link href="http://machty.github.com/"/>
<updated>2014-11-15T13:15:08-05:00</updated>
<id>http://machty.github.com/</id>
<author>
<name><![CDATA[Alex Matchneer]]></name>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[DRY and Tie Your Ember Routes]]></title>
<link href="http://machty.github.com/blog/2012/09/28/dry-your-ember-routes/"/>
<updated>2012-09-28T13:54:00-04:00</updated>
<id>http://machty.github.com/blog/2012/09/28/dry-your-ember-routes</id>
<content type="html"><![CDATA[<h4>UPDATE 10/1/12: see bottom the post for avoiding infinite loop issues</h4>
<h4>UPDATE 1/6/12: this post is deprecated since the new <a href="http://emberjs.com/guides/routing/defining-your-routes/">Ember Router v2 API</a> came out.</h4>
<p>A common complaint about Ember.js routing is that seems to force you to
create pairs of extremely light-weight, useless Views and Controllers
for each new route you create. Another is that it's not particular
obvious how data ought to be shared between routes/controllers. Here's
one way to cut down on the seeming boilerplate while linking your data.</p>
<!--more-->
<p>Consider the following example router
that describes an application that allows you to create new Campaigns,
but the forms for filling out all the information about a Campaign are
split between multiple pages.</p>
<p><div class='bogus-wrapper'><notextile><figure class='code'><figcaption><span>app_router.js.coffee </span></figcaption>
<div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
</pre></td><td class='code'><pre><code class='coffeescript'><span class='line'><span class="nv">App.Router = </span><span class="nx">Em</span><span class="p">.</span><span class="nx">Router</span><span class="p">.</span><span class="nx">extend</span>
</span><span class='line'> <span class="nv">root: </span><span class="nx">Em</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="o"></</span><span class="nx">p</span><span class="o">></span>
</span><span class='line'>
</span><span class='line'><span class="o"><</span><span class="nx">pre</span><span class="o">><</span><span class="nx">code</span><span class="o">></span><span class="c1"># ... index route, redirect-to's, etc.</span>
</span><span class='line'>
</span><span class='line'><span class="nv">new_campaign: </span><span class="nx">Em</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span>
</span><span class='line'> <span class="nv">route: </span><span class="s">'/new_campaign'</span>
</span><span class='line'> <span class="nv">connectOutlets: </span><span class="p">(</span><span class="nx">router</span><span class="p">)</span> <span class="o">-&</span><span class="nx">gt</span><span class="p">;</span>
</span><span class='line'> <span class="c1"># 1. Instantiate a NewCampaignController and NewCampaignView,</span>
</span><span class='line'> <span class="c1"># and inject the latter into applicationController's outlet.</span>
</span><span class='line'> <span class="c1"># also create the empty Campaign object that will be </span>
</span><span class='line'> <span class="c1"># shared between the multiple pages of forms and pass it in</span>
</span><span class='line'> <span class="c1"># as the context object to the controller.</span>
</span><span class='line'> <span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s">'applicationController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s">'newCampaign'</span><span class="p">,</span> <span class="nx">App</span><span class="p">.</span><span class="nx">Campaign</span><span class="p">.</span><span class="nx">createRecord</span><span class="p">())</span>
</span><span class='line'>
</span><span class='line'> <span class="nv">basic_info: </span><span class="nx">Em</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span>
</span><span class='line'> <span class="nv">route: </span><span class="s">'/basic_info'</span>
</span><span class='line'> <span class="nv">connectOutlets: </span><span class="p">(</span><span class="nx">router</span><span class="p">)</span> <span class="o">-&</span><span class="nx">gt</span><span class="p">;</span>
</span><span class='line'> <span class="c1"># 2. Create BasicInfoController and BasicInfoView, inject</span>
</span><span class='line'> <span class="c1"># into the NewCampaignView outlet.</span>
</span><span class='line'> <span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s">'newCampaignController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s">'basicInfo'</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'> <span class="nv">more_info: </span><span class="nx">Em</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span>
</span><span class='line'> <span class="nv">route: </span><span class="s">'/more_info'</span>
</span><span class='line'> <span class="nv">connectOutlets: </span><span class="p">(</span><span class="nx">router</span><span class="p">)</span> <span class="o">-&</span><span class="nx">gt</span><span class="p">;</span>
</span><span class='line'> <span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s">'newCampaignController'</span><span class="p">).</span><span class="nx">connectOutlet</span><span class="p">(</span><span class="s">'moreInfo'</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'> <span class="c1">#... a third page</span>
</span><span class='line'><span class="o"></</span><span class="nx">code</span><span class="o">></</span><span class="nx">pre</span><span class="o">></span>
</span><span class='line'>
</span><span class='line'><span class="o"><</span><span class="nx">p</span><span class="o">></span>
</span></code></pre></td></tr></table></div></figure></notextile></div></p>
<p>There's two main problems with the code above:</p>
<ol>
<li>The <code>basic_info</code> and <code>more_info</code> child state controllers haven't yet
been connected to the Campaign object created for the parent
<code>new_campaign</code> state.</li>
<li>Already, you've got to implement a bunch of nearly empty Views and
Controllers, namely:</li>
</ol>
<p><div class='bogus-wrapper'><notextile><figure class='code'><figcaption><span>app_router.js.coffee </span></figcaption>
<div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='coffeescript'><span class='line'><span class="nv">App.NewCampaignView = </span><span class="nx">Em</span><span class="p">.</span><span class="nx">View</span><span class="p">.</span><span class="nx">extend</span>
</span><span class='line'> <span class="nv">templateName: </span><span class="s">"new_campaign"</span>
</span><span class='line'><span class="nv">App.NewCampaignController = </span><span class="nx">Em</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="o"></</span><span class="nx">p</span><span class="o">></span>
</span><span class='line'>
</span><span class='line'><span class="o"><</span><span class="nx">p</span><span class="o">></span><span class="nv">App.BasicInfoView = </span><span class="nx">Em</span><span class="p">.</span><span class="nx">View</span><span class="p">.</span><span class="nx">extend</span>
</span><span class='line'> <span class="nv">templateName: </span><span class="s">"basic_info"</span>
</span><span class='line'><span class="nv">App.BasicInfoController = </span><span class="nx">Em</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="o"></</span><span class="nx">p</span><span class="o">></span>
</span><span class='line'>
</span><span class='line'><span class="o"><</span><span class="nx">p</span><span class="o">></span><span class="nv">App.MoreInfoView = </span><span class="nx">Em</span><span class="p">.</span><span class="nx">View</span><span class="p">.</span><span class="nx">extend</span>
</span><span class='line'> <span class="nv">templateName: </span><span class="s">"more_info"</span>
</span><span class='line'><span class="nv">App.MoreInfoController = </span><span class="nx">Em</span><span class="p">.</span><span class="nx">Controller</span><span class="p">.</span><span class="nx">extend</span><span class="p">()</span>
</span></code></pre></td></tr></table></div></figure></notextile></div></p>
<p>First off, if you're a 1-to-1 class-to-file kind of person, your
codebase, app namespace, and editor tab space will bloat with nearly
empty View and Controller definitions. Sucks.</p>
<p>Fortunately, if the <code>connectOutlet</code> function comes in all sorts of
flavors, one where you you can pass in an options hash rather than
a string to specify exactly which Views / Controllers to create and what
your context option should be. Let us kill off the <code>BasicInfo</code>- and <code>MoreInfo</code>-
Controllers via the following:</p>
<p><div class='bogus-wrapper'><notextile><figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='coffeescript'><span class='line'><span class="o"></</span><span class="nx">p</span><span class="o">></span>
</span><span class='line'>
</span><span class='line'><span class="o"><</span><span class="nx">h1</span><span class="o">></span><span class="nv">App.Router = </span><span class="p">...</span><span class="o"></</span><span class="nx">h1</span><span class="o">></span>
</span><span class='line'>
</span><span class='line'><span class="o"><</span><span class="nx">pre</span><span class="o">><</span><span class="nx">code</span><span class="o">></span> <span class="nv">basic_info: </span><span class="nx">Em</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span>
</span><span class='line'> <span class="nv">connectOutlets: </span><span class="p">(</span><span class="nx">router</span><span class="p">)</span> <span class="o">-&</span><span class="nx">gt</span><span class="p">;</span>
</span><span class='line'> <span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s">'newCampaignController'</span><span class="p">).</span><span class="nx">connectOutlet</span>
</span><span class='line'> <span class="nv">viewClass: </span><span class="nx">App</span><span class="p">.</span><span class="nx">BasicInfoView</span>
</span><span class='line'>
</span><span class='line'> <span class="nv">more_info: </span><span class="nx">Em</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span>
</span><span class='line'> <span class="nv">connectOutlets: </span><span class="p">(</span><span class="nx">router</span><span class="p">)</span> <span class="o">-&</span><span class="nx">gt</span><span class="p">;</span>
</span><span class='line'> <span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s">'newCampaignController'</span><span class="p">).</span><span class="nx">connectOutlet</span>
</span><span class='line'> <span class="nv">viewClass: </span><span class="nx">App</span><span class="p">.</span><span class="nx">MoreInfoView</span>
</span><span class='line'><span class="o"></</span><span class="nx">code</span><span class="o">></</span><span class="nx">pre</span><span class="o">></span>
</span><span class='line'>
</span><span class='line'><span class="o"><</span><span class="nx">p</span><span class="o">></span>
</span></code></pre></td></tr></table></div></figure></notextile></div></p>
<p>Turns out we've actually solved both problems: we no longer need
<code>BasicInfoController</code> or <code>MoreInfoController</code>, and we've actually
tied the templates for <code>BasicInfoView</code> and <code>MoreInfoView</code> correctly
to the Campaign object in NewCampaignController, and how exactly does
that work?</p>
<h3>Default Context Resolution: parentView</h3>
<p>Well, ever since
<a href="https://gist.github.com/2494968">Ember changed the way it resolves contexts</a>,
the context of a template is determined, by default, by looking up the
<code>controller</code> property of the View and using that if it exists, otherwise
it checks up the chain of <code>parentView</code>s to see if any of them have
defined a <code>controller</code> property to use as the context. <code>BasicInfoView</code>
and <code>MoreInfoView</code> don't have controllers set (since we're using the
<code>viewClass</code>-only form of <code>connectOutlet</code>), so their contexts resolve to
their <code>parentView</code>'s <code>controller</code>, namely <code>NewCampaignController</code>.
Therefore, the Handlebars templates for <code>BasicInfoView</code> and
<code>MoreInfoView</code> now use the <code>Campaign</code> object as their context.</p>
<h3>Can we do better?</h3>
<p>You might be itching to get rid of <code>BasicInfo-</code> and <code>MoreInfoView</code> as
well, since all they do is specify their <code>templateName</code>. You could do
this, if you wanted, by doing
<code>viewClass: Em.View.extend(templateName: "basic_info")</code>, but that seems
like overkill to me. Plus, you'd lose the benefits of being able to,
say, automatically focus a text field via the View's <code>didInsertElement</code>
hook.</p>
<h3>Where can I learn more?</h3>
<p>Documentation on Ember routing is still pretty meager, so I'd definitely
encourage you to really dig into the Ember source, particularly the test
cases that deal with these kinds of issues. For example, tucked right in
the middle of <code>controller_test.js</code> is</p>
<p><div class='bogus-wrapper'><notextile><figure class='code'><figcaption><span>app_router.js.coffee </span></figcaption>
<div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">test</span><span class="p">(</span><span class="s2">"if the controller is explicitly set to null while connecting an outlet, the instantiated view will inherit its controller from its parent view"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="p">...</span>
</span></code></pre></td></tr></table></div></figure></notextile></div></p>
<p>Which isn't exactly what's happening regarding context resolution, but still clued me into this
approach.</p>
<h3>Update: Infinite Loop Gotcha</h3>
<p>I've been refactoring like a madman since discovering this and in some
cases I was getting stack overflows that were hard to trace down, but I
found the cause: if the <code>viewClass</code> you specify has an unnamed
<code>outlet</code> in its template, you'll get an infinite loop. I'm 90% sure
the reason why is that when you use the <code>viewClass</code>-only form of
connectOutlet, the default outlet name resolves to <code>view</code> (the default)
and steps on the namespace toes of the parent view outlet that 1) had
the same name (view) and was 2) (more importantly) tied to the same
single controller. So, there's namespace collisions. You can avoid this
by either getting rid of the leaf view's <code>outlet</code> (which might be
the case if you've been unquestioningly throwing outlets into templates
that don't actually inject child views... we're probably all guilty of
this at some point in our struggles with the router), or, if you
actually need the outlet, just give it a sensible name that won't
collide with any other outlet attached to the shared controller. Then
when you want to inject a view into <em>that</em> outlet, you can do:</p>
<p><div class='bogus-wrapper'><notextile><figure class='code'><figcaption><span>app_router.js.coffee </span></figcaption>
<div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
</pre></td><td class='code'><pre><code class='coffeescript'><span class='line'><span class="o"></</span><span class="nx">p</span><span class="o">></span>
</span><span class='line'>
</span><span class='line'><span class="o"><</span><span class="nx">p</span><span class="o">></span> <span class="nv">basic_info: </span><span class="nx">Em</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span><span class="o"></</span><span class="nx">p</span><span class="o">></span>
</span><span class='line'>
</span><span class='line'><span class="o"><</span><span class="nx">pre</span><span class="o">><</span><span class="nx">code</span><span class="o">></span><span class="nv">route: </span><span class="s">'/basic_info'</span>
</span><span class='line'><span class="nv">connectOutlets: </span><span class="p">(</span><span class="nx">router</span><span class="p">)</span> <span class="o">-&</span><span class="nx">gt</span><span class="p">;</span>
</span><span class='line'> <span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s">'newCampaignController'</span><span class="p">).</span><span class="nx">connectOutlet</span>
</span><span class='line'> <span class="nv">viewClass: </span><span class="nx">App</span><span class="p">.</span><span class="nx">BasicInfoView</span>
</span><span class='line'>
</span><span class='line'><span class="nv">index: </span><span class="nx">Em</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span>
</span><span class='line'> <span class="nv">route: </span><span class="s">'/'</span>
</span><span class='line'>
</span><span class='line'><span class="nv">captcha: </span><span class="nx">Em</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nx">extend</span>
</span><span class='line'> <span class="nv">route: </span><span class="s">'/captcha'</span>
</span><span class='line'> <span class="nv">connectOutlets: </span><span class="p">(</span><span class="nx">router</span><span class="p">)</span> <span class="o">-&</span><span class="nx">gt</span><span class="p">;</span>
</span><span class='line'> <span class="c1"># Assume theres an outlet named captcha in the basic_info template.</span>
</span><span class='line'> <span class="nx">router</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s">'newCampaignController'</span><span class="p">).</span><span class="nx">connectOutlet</span>
</span><span class='line'> <span class="nv">viewClass: </span><span class="nx">App</span><span class="p">.</span><span class="nx">CaptchaView</span>
</span><span class='line'> <span class="nv">outletName: </span><span class="s">'captcha'</span>
</span><span class='line'><span class="o"></</span><span class="nx">code</span><span class="o">></</span><span class="nx">pre</span><span class="o">></span>
</span><span class='line'>
</span><span class='line'><span class="o"><</span><span class="nx">p</span><span class="o">></span>
</span></code></pre></td></tr></table></div></figure></notextile></div></p>
]]></content>
</entry>
</feed>