-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathatom.xml
More file actions
1352 lines (1000 loc) · 114 KB
/
atom.xml
File metadata and controls
1352 lines (1000 loc) · 114 KB
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[Instructure Tech Blog]]></title>
<link href="http://instructure.github.io/atom.xml" rel="self"/>
<link href="http://instructure.github.io/"/>
<updated>2021-06-02T13:06:34-05:00</updated>
<id>http://instructure.github.io/</id>
<author>
<name><![CDATA[Instructure]]></name>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[Swift: Just Follow the Types]]></title>
<link href="http://instructure.github.io/blog/2015/02/05/just-follow-the-types/"/>
<updated>2015-02-05T14:11:00-06:00</updated>
<id>http://instructure.github.io/blog/2015/02/05/just-follow-the-types</id>
<content type="html"><![CDATA[<p>As Mac and iOS craftsmen we’ve been given a new tool. In some ways it looks a lot like the old tool, a tool we’ve been using for many years now. In many ways it is very different. This tool is, of course, the Swift programming language.</p>
<p>A strict type system is one thing that differentiates Swift from Objective-C.<!-- more --> This type system, along with other related features like generic types and custom operators, has started a movement among developers to adopt some techniques that Swift’s predecessor made difficult or impossible. Some of these language features and techniques come from the language Haskell, one of the many languages Chris Lattner <a href="http://nondot.org/sabre/">drew inspiration from in creating Swift</a>.</p>
<p><img class="right half" src="https://farm3.static.flickr.com/2658/4293485922_7474ef04ce.jpg" width="375" height="500" title="An old rusty tool." alt="An old rusty tool."></p>
<p>As I try to learn and adopt these new techniques I feel a bit like a woodworker who’s been given a new tool, one with new features that I don’t completely understand. One trick I’ve learned is that with such a strong type system comes certain affordances.</p>
<p>Let’s look at an example.</p>
<p><code>Optional</code> has a method called <code>map</code>. Now if you’ve used <code>map</code> functions that are common for collection types you might be able to intuit what this map function does. An <code>Optional</code> isn’t an array, however, so it might still be a little confusing. But if you take a moment to inspect the type of <code>map</code> you will see the trail of breadcrumbs.</p>
<figure class='code'><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=''><span class='line'>func map<U>(f: (T) -> U) -> U?</span></code></pre></td></tr></table></div></figure>
<p>Here we can see that map takes a single function from <code>(T)->U</code> as a parameter and returns an <code>U?</code> (which is just nice syntax for <code>Optional<U></code>) as a result. Remember that <code>T</code> is the generic parameter type of the <code>Optional</code> on which you are calling <code>map</code>. This effectively allows us to take an object wrapped up in an <code>Optional</code> <a href="http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html">context</a> and apply a function to it resulting in an <code>Optional</code> object of the given function’s return type.</p>
<p>Higher order functions like <code>map</code> are common in functional programming languages. For example, Haskell defines a function <code>fmap</code> that performs the same operation as <code>Optional.map</code>. Haskell programmers use this function so often that they’ve even given it an operator: <code><$></code>, and many Swift programmers are <a href="https://github.com/typelift/swiftz">following suit</a> (though, due to some limitations on special characters in Swift, most implementations seem to have settled on <code><^></code> for that paricular operation).</p>
<p>When confronted with new symbols and <a href="http://chris.eidhof.nl/posts/json-parsing-in-swift.html">unfamiliar techniques for solving problems</a> in a strictly typed language like Swift, I find it helps to follow the types.</p>
<p>Photo by <a href="https://www.flickr.com/photos/hortulus_aptus/">Seán A. O’Hara</a>. Licensed under <a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a>.</p>
<div class="about-box">
<img src="http://0.gravatar.com/avatar/43f93ddd5058f44ffe579d923945117a" class="avatar" align="left" />
<p>
Derrick Hathaway is a Sr. Mobile Engineer at Instructure. He is currently working on making Instructure’s iOS apps more awesome! <br />
<a href="https://github.com/derrh">Github</a> and
<a href="http://twitter.com/derrh">Twitter</a>.
</p>
</div>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Breaking the Rules: Accessible Dialog Components]]></title>
<link href="http://instructure.github.io/blog/2014/09/25/accessible-dialogs/"/>
<updated>2014-09-25T09:00:00-05:00</updated>
<id>http://instructure.github.io/blog/2014/09/25/accessible-dialogs</id>
<content type="html"><![CDATA[<p>Turns out, <code>role="dialog"</code> isn’t your friend.</p>
<h2>Cursor Modes</h2>
<p>When you first start building accessible websites, you learn quickly
that there are different cursor modes when using Windows screen readers,
<a href="http://tink.co.uk/2014/09/understanding-screen-reader-interaction-modes/">Léonie Watson explains them well here</a>.</p>
<p>Certain element roles, like <code>application</code> and <code>dialog</code> will change the
mode of the cursor. Very often this is not what you want. Yes, It does
allow the developer to implement convenient keyboard events for the
component, but it disables the normal keyboard navigation shortcuts for
screen reader users. For sighted users, that would be a lot like opening
your page up in Internet Explorer 5.5 on a mac.</p>
<!--more-->
<h2>The Problem With ARIA Dialogs</h2>
<p>Unlike other components, dialogs and modals usually just contain regular
content like the rest of the website. They don’t need special keyboard
navigation implementations, except for handling the escape key to
request to close it.</p>
<p>When you use <code>role="dialog"</code> the forms/focus cursor mode is activated
and the screen reader user can’t interact with the content in the dialog
like they can with the rest of the website. In most cases, this results
in much of the content in the dialog becoming completely inaccessible to
them.</p>
<h2>The Solution</h2>
<p>The solution is to “hide” everything in the application—except the
dialog—from the screen reader. This way the dialog can be navigated
normally and the rest of the app is inaccessible until the dialog closes.</p>
<p>In a nutshell:</p>
<ol>
<li>Don’t use <code>role="dialog"</code>, to avoid switching cursor modes.</li>
<li>When a dialog opens, set <code>aria-hidden="true"</code> on the rest of the
page and focus the content of the modal.</li>
<li>When a dialog closes, remove the <code>aria-hidden</code> attribute and return
focus to the element that initiated it.</li>
</ol>
<p>The easiest thing to do is simply wrap your entire website in a
containing element and then just manage <code>aria-hidden</code> on that one
element instead of many.</p>
<p>It would end up looking something like this:</p>
<figure class='code'><figcaption><span></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>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt"><div</span> <span class="na">id=</span><span class="s">"app"</span><span class="nt">></span> ... <span class="nt"></div></span>
</span><span class='line'><span class="nt"><div</span> <span class="na">class=</span><span class="s">"dialog"</span><span class="nt">></span> ... <span class="nt"></div></span>
</span></code></pre></td></tr></table></div></figure>
<p>And when the dialog is open:</p>
<figure class='code'><figcaption><span></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>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt"><div</span> <span class="na">id=</span><span class="s">"app"</span> <span class="na">aria-hidden=</span><span class="s">"true"</span><span class="nt">></span> ... <span class="nt"></div></span>
</span><span class='line'><span class="nt"><div</span> <span class="na">class=</span><span class="s">"dialog"</span><span class="nt">></span> ... <span class="nt"></div></span>
</span></code></pre></td></tr></table></div></figure>
<h2>But That’s Not The Spec!</h2>
<p>The spec probably needs to change to support dialogs where forms mode
doesn’t make sense. We implemented this in order to get Canvas certified
as an accessible web application. The web moves fast and the specs don’t
always keep up.</p>
<p>Until the specs catch up, and all the parties involved implement them,
we choose to focus on the experience of our users (oh, right, and
<a href="http://webaim.org/services/certification/">getting certified</a>).</p>
<div class="about-box">
<img src="http://www.gravatar.com/avatar/749001c9fe6927c4b069a45c2a3d68f7?s=100" class="avatar" align="left" />
<p>
Ryan Florence heads up the Web Frameworks team at Instructure where
they chase both squirrels and JavaScript frameworks, inviting the
rest of the team to join them. Follow his adventures on
<a href="https://github.com/rpflorence">Github</a> and
<a href="http://twitter.com/ryanflorence">Twitter</a>.
</p>
</div>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Balanced Product Management]]></title>
<link href="http://instructure.github.io/blog/2014/08/04/balanced-product-management/"/>
<updated>2014-08-04T09:54:00-05:00</updated>
<id>http://instructure.github.io/blog/2014/08/04/balanced-product-management</id>
<content type="html"><![CDATA[<p>If you look at job descriptions and blogs about product-y things, there are lots of opinions about what makes a great product manager. They talk about owning strategy, roadmaps, markets, and plans. You’re like the CEO of your product and use your über creativity to find the special sauce and make all the decisions. Then everyone will be on board with your vision, singing your praises, and sparkly rainbow unicorns will lead your company down the path to IPO.</p>
<p>However, all of that Being in Charge may actually be counterproductive to the mindset that will make you most successful—Not Being in Charge. The truth is, everyone else’s input is far more important than mine.</p>
<!--more-->
<p>“But,” you say, “product is supposed to be the mini-CEO and the vision, right? How can you be the vision if you’re not deciding all the things?”</p>
<p>Here’s the secret—what I want doesn’t matter. The only thing that will make us win as a company is making an easier or better way for other people to do what they already do. That means that a good product person works for everyone. Users and buyers of our software (of course), engineers, support, client services, designers, salespeople, QA, executives, marketing—they’re are all my boss. What they need comes way before what I want.</p>
<p>The trick is in the balance. A good product person keeps an eye on where the market is going, what users need today, things that are confusing or broken, code that’s old and crusty, what’s losing sales to competitors, where the terrible UX/UI is, and all the executive initiatives.</p>
<p><img class="right half" src="http://instructure.github.io/images/product_eyes.jpg" width="400" height="400" title="This is the wrong way to do product." alt="Many-eyed monster with OK on its shirt."></p>
<p>However, having all those bosses doesn’t mean you just do what they tell you to do. The other side to the balance is knowing enough about everything to be able to prioritize, say “no,” and live with your bosses’ inevitable disapproval.</p>
<p>Bug fixes bore marketing. Sales features add complexity for support. Engineers are sad working in ancient code. Executives need this thing to make money. To top it off, no one’s asking for what you know they’ll need next year or recognizes the magic in your <a href="http://brandinfluencegroup.com/blog/henry-ford-innovation-and-that-faster-horse-quote-patrick-vlaskovits-harvard-business-review/">new car idea</a>.</p>
<p>Sound difficult? It is. You have to be passionate about supporting everyone and really feel their pain. You have to be patient enough to wait if the time isn’t right and spend far more time listening than you do talking. You also have to be okay with very delayed reinforcement. But sometimes, when you get the balance just right, you really do get to be part of making a dent in the world.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Approaching Accessibility Testing]]></title>
<link href="http://instructure.github.io/blog/2014/06/17/approaching-accessibility-testing/"/>
<updated>2014-06-17T16:37:00-05:00</updated>
<id>http://instructure.github.io/blog/2014/06/17/approaching-accessibility-testing</id>
<content type="html"><![CDATA[<p>When I first started learning about accessibility, I had no idea what I was doing. I am a sighted person, and didn’t know if what I was doing was correct. I fed a URL to an online accessibility evaluator, which then told me what elements were missing on the page (ie: an image did not have an alt tag). It wasn’t until I met a blind person that I started to understand how people with visual impairments actually use the web. Over the years, I picked up a lot of insight on how to conduct accessibility testing, which I want to share with you.</p>
<p> <!--more--></p>
<p>Before I dive into the “tips and tricks”, I have to dispell a few myths:</p>
<h2>Accessibility Myths</h2>
<h3>Accessibility only affects blind people</h3>
<p>Blind people are not the only ones affected by inaccessible websites; they affect people with motor impairments, color blindness, or cognitive impairments, amongst others. Accessibility testing is necessary because we as human beings should all have access to the web. If you or your company is not doing accessibility testing, bring it up at the next meeting.</p>
<h3>Assistive technology is super cheap</h3>
<p>Many people rely on screenreading software or a braille display, and it can get very expensive. The license for JAWS, a popular screenreading software, costs anywhere from $895 USD to $1,095 USD, and another couple hundred dollars if you want to buy the upgrade that comes out once each year. Choosing to skip an upgrade may mean that the latest version of your web browser may not work well, or at all. A braille display, which is a device that displays braille using pins that move up and down (a necessity if you are deafblind), can cost thousands of dollars. And this is only for your computer – you have to pay more money on top of that for your mobile devices. Imagine if you had to pay for all that, and it only worked part of the time. It’s like buying a car that only runs on Tuesdays.</p>
<h3>Assistive technology is always up to date</h3>
<p>Technology changes so fast that, when we buy the newest smartphone, it becomes outdated months later. In the same way, assistive technology is often behind new web technologies. Screenreaders can’t keep up with the fast pace of web development, which includes new features every other week.</p>
<h3>All screenreaders are the same, I only have to test with one</h3>
<p>Like browsers, not all screenreading software is alike. Mac OS X has a built in screenreader called “VoiceOver”, which you can enable in the Accessibility section of your System Preferences. It also includes a comprehensive guided tutorial. For Windows, two popular options are JAWS and NVDA. NVDA is free and open source, and is available in many languages. For Linux users, Orca, which is also free and open source, is a great option. The screenreader output can greatly vary; when you navigate to a “Close” button on a modal window, you might hear “Button Close Button” from one, but hear “Close Button” from the other. Talk with your users and see what they are using, and if your budget allows, test with as many screenreaders as you can. At Instructure, we test Canvas using the latest version of VoiceOver and JAWS, and are currently evaluating NVDA.</p>
<h3>Accessibility testing is the worst kind of testing</h3>
<p>Testing of any kind can be painful, whether it be writing unit tests or doing user experience testing. Accessibility testing is also on that list, but realize the more you test, the more aware you will be when writing your code. You will start to look out for things here and there because you had to spend twenty minutes trying to navigate to a submenu in a previous commit.</p>
<h3>I can just throw my website at an accesibility evaluator and call it good</h3>
<p>An evaluator only looks at the structure of your site; it doesn’t emulate the user experience. We all know that, sometimes, what looks good on paper doesn’t work out in real life. The same goes for an evaluator. It will tell you if your headers are out of order, but it won’t tell you if your navigation is terrible because you are inconsistent with what keypress you are using for a certain element. You need actual people to test your site out. Which brings me to my next point…</p>
<h3>Only blind people can do accessibility testing</h3>
<p>Anyone who learns the software or hardware can do accessibility testing. At Instructure, we have our Quality Assurance (QA) engineers do a lot of the testing, and all of them are sighted. If you have questions and would like to ask someone who is blind, there are a lot of resources out there. For those in the education sector, many universities have a disability resource center – reach out to the coordinator there, and ask for permission to get feedback. Your city or state may also have a dedicated center. We also have the Internet for a reason; use it!</p>
<h2>Where to start and how to test for accessibility</h2>
<p>If you haven’t already, run your site through an evaluator. There are many free accessibility evaluators online, where you feed the URL into an evaluator, and it tells you how accessible the page is. Two popular evaluators are <a href="http://wave.webaim.org/" title="WAVE Evaluator">WAVE</a> and <a href="http://fae.cita.uiuc.edu/" title="FAE Evaluator">FAE</a>. If you use FAE, note that it is evaluating in accordance with the Illlinois Information Technology Accessibility Act. Not all evaluators are running against the same guidelines, so find out what it is evaluating against. Many evaluators will use the <a href="http://www.w3.org/TR/WCAG/" title="W3C Guidelines">W3C Web Content Accessibility Guidelines</a>. The evaluator will show you any errors and provide feedback. Do you have redudant title text? Are your forms labeled? These are items that will be pointed out to you, which you can easily fix.</p>
<p>Once you have your baseline, and fixed the structural elements, it’s time to move on to testing with real people. Ask for feedback from your users and see what needs the most attention, and start there. Here are a few of the things we do:</p>
<p>1) On each page we check to see if there are headers, a main landmark, and a complementary landmark (if applicable). We also check for to see if there are any lists, forms, and tables on the page, and use the appropriate screenreader key. Many users navigate using these elements to quickly jump to content on a page. It’s hard to scroll through an entire page, so make sure headers and landmarks are available for easy access.</p>
<p>2) We try to use the keyboard as much as possible. Keyboard navigation is critical to many users, especially those with physical disabilities. Not everyone can use a trackpad for gestures, but most everyone has a keyboard.</p>
<p>3) We heavily reference the keyboard layout and shortcuts provided by the screenreader we are using. When I first started accessibility testing, I used the tab key to go through the page. I didn’t realize there were screenreader users and keyboard only users. If you are unsure of which keys correspond with which action, look up the keyboard layout for your respective screenreader.</p>
<p>4) We look to see if our elements have consistency; not all of the elements on our pages share the same keypress, even if they look the same visually (which, we are working on). For example, don’t have a “Submit” button that reads as a link on one page, and as a button on another. Make sure your elements are all the same under the hood.</p>
<p>5) With links and buttons, we always make sure a user can read the link/button text and execute the appropriate action. Sometimes it will only read the link text and won’t open the link. Don’t assume something works just because you can navigate to it.</p>
<p>6) We look at a page from top to bottom, then outline all the elements in a spreadsheet. For example, under “main body navigation”, I would have “Reads Assignment Title”, “Reads Published Button”, and etc. Organizing and writing out all the elements and the location make it much easier for other people to read and follow your work.</p>
<p>What we do at Instructure isn’t limited to this list; this is merely a springboard for you to start. We also conduct accessibility audits, and actively work with our clients and other vendors. Instructure is also fortunate to have a dedicated accessibility team in addition to engineers who are passionate about accessibility on each individual core team of Canvas.</p>
<p>Tackling accessibility testing may seem like a daunting task, but start with the steps outlined above. Your changes will start out small, and it may seem insignificant, but realize that every effort you make changes the web to be more accessible. Soon, you will be the one teaching others about what you’ve learned, and how to make an accessible product. We at Canvas hope that one day, we’ll be reading a blog post from you.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Too big to test: combating test apathy in legacy code]]></title>
<link href="http://instructure.github.io/blog/2014/05/14/too-big-to-test-combating-test-apathy-in-legacy-code/"/>
<updated>2014-05-14T10:53:00-05:00</updated>
<id>http://instructure.github.io/blog/2014/05/14/too-big-to-test-combating-test-apathy-in-legacy-code</id>
<content type="html"><![CDATA[<p>Writing tests for large Rails apps with lots of dependencies and complicated modeling is, without question, a complete nightmare. We often spend more time wrestling with tests than we do writing code. The end result of writing tests for legacy code is unfortunately predictable: a test suite full of holes, poor coverage, and tests that aren’t actually testing the thing you think they are. Bugs begin to pile up, technical debt is avoided like the plague, and quick-fix bandaids are applied instead of addressing the problems head on.</p>
<p>We are then faced with a dilemma: our app has become too big and too complicated to test, and we no longer want to write tests for it because it is so painful. This is unavoidable. No matter how many conference talks we attend that promise to teach us how to writing clean, maintainable code, we’re still drowning in a bog of bad. The reality isn’t that we’re bad developers, it’s that we don’t dictate our workload, deadlines, or priorities. We have to make sacrifices in our code, which isn’t a bad thing until it is. So how do we fix it? How do we take away the pain?</p>
<p>The short answer: it’s not easy to fix, and it takes time.</p>
<!--more-->
<h3>Prioritize technical test debt</h3>
<p>There is no sense in prioritizing technical debt if you have technical test debt. No one wants to write tests for a messy test suite, and you can’t fix problems in your code if the tests are contributing to them. Start chipping away at test apathy by improving the test writing and running experience. There are a few ways to quickly improve your testing environment.</p>
<p><strong>Clean up your factories and spec helpers</strong></p>
<p>Remove redundant factories or duplicate spec helpers. Helpers with miles of conditionals don’t actually <em>help</em> anyone. Consider refactoring any method that takes a hash of options to be more semantically named. Search for factory settings or attributes, or helper methods arguments, you often find yourself using and make them the defaults instead of a passed argument.</p>
<p>If you’re using a homegrown factory system instead of a gem like <a href="https://github.com/thoughtbot/factory_girl">FactoryGirl</a>, considering refactoring your factories or completely re-writing them. Lots of bad or unused factories can accumulate when you have many developers working on the same code base.</p>
<p><strong>Using Selenium? Delete your specs</strong></p>
<p><em>… and re-write them</em>, preferably with a Selenium wrapper, such as <a href="https://github.com/jnicklas/capybara">Capybara</a>. Using a wrapper will also helper clean up your cluttered helper files for these specs.</p>
<p>Selenium specs are a source of constant pain and heartache for developers: they take a long time to run, and they’re incredibly complicated. It’s a lot of test setup and clicking on stuff for <em>one</em> assertion. In lieu of comprehensive Selenium specs that handle all of your edge cases, improve your unit test coverage (on both the client- and server-side), consider writing end-to-end specs for happy paths, and invest in manual testing where people actually click on things. Simplify your Selenium specs and improve your developers’ happiness by only testing your happy paths and expected error messages.</p>
<p><strong>Organize your spec files properly</strong></p>
<p>Do everyone a favor and fix your nesting. If you’re using RSpec, you shouldn’t have a <code>describe</code> block with a <code>context</code> that has two more sets of nested <code>context</code>s. If a <code>describe</code> or <code>context</code> block only has one test in it, consider re-organizing to incorporate it with other tests. If you’re writing unit tests, use the <code>#my_method_name</code> syntax to group your tests together. Re-write tests that contain multiple assertions: why do you need to assert two things in one test? Assertions that loop through arrays or hashes are sometimes necessary, but they should only be checking one thing, not multiple attributes of your objects.</p>
<p>Lastly, re-organize your file so it is in roughly the same order as your code. The first method in your class shouldn’t be the second-to-last thing tested. A little file organization goes a long way when it comes to debugging and adding new tests.</p>
<h3>Use your tools well</h3>
<p>A hammer is not a screwdriver, nor is it a crowbar: don’t make your developers be the whole toolbox.</p>
<p><strong>QA Analysts: real humans you need</strong></p>
<p>Your developers are not your users: they’re your developers. While it is reasonable to expect a level of familiarity with your software, developers ultimately are not the ones using it on a day-to-day basis. They are not the experts on your software. You need experts, and they’re not your developers.</p>
<p>Automation is all fine and dandy, but you can’t automate people: we’re unpredictable and kind of stupid. The huge value in manual QAs and regression tests are that you can actually test your code against the human element. Your QAs Analysts are neither unpredictable nor stupid, but they can replicate that element of humans more realistically than automated tests can.</p>
<p><strong>Regression tests are not a bandaid</strong></p>
<p>Regression tests aren’t just good for finding bugs: they are a tool that can help you spot the holes in your test suite. A bug fix for a regression test shouldn’t just be a fix to the code, it should also repair the holes in your tests. Dig through your spec files: is the spec for that bug missing? Is it testing the wrong thing? Is the setup wrong? Fix it.</p>
<p><strong>Don’t monkey patch it</strong></p>
<p>Use the gems, libraries, and tools as they are actually intended. Your test suite will rapidly spiral out of control (again) if you find yourself ripping apart your tools.</p>
<h3>Maintenance mode</h3>
<p>Fixing bad tests and cleaning up ugly factories is all well and good, but what happens long term? Maintaining a test suite is just as important as maintaining code. If you walk away from your test suite after putting in so much effort, it will be all:</p>
<p><img src="http://25.media.tumblr.com/b60af898d2636d792de3089108562c91/tumblr_mt0qdlrZMR1siookko1_500.gif" alt="Miley Cyrus: "Don't you every say I just walked away, I will always want you."" /></p>
<p>Don’t wreck your test suite by ignoring it.</p>
<p><strong>Don’t be afraid of repeating yourself</strong></p>
<p>Sometimes DRY code is bad code, especially if you’re creating unnecessary objects or making redundant database calls in your test setup. Clean up the setup that runs before each test, and make sure you’re only creating what you need to.</p>
<p><strong>Abstract after the fact</strong></p>
<p>In the (paraphrased) <a href="https://speakerdeck.com/skmetz/all-the-little-things-rubyonales">words of Sandi Metz</a>: it is better to repeat yourself than to abstract the wrong thing. You don’t have the perspective to abstract the right thing at the very beginning of your refactoring and cleaning process. To ensure you’re not recreating the problem you’re trying to fix, periodically block off time to review spec files for duplicate code that can be extracted. It takes time and hindsight, which is why reviewing spec files periodically is just as important as refactoring legacy code.</p>
<p><strong>Remember that tests are code, too</strong></p>
<p>Consider these two quotes:</p>
<blockquote><p>“Ugh, I have to write code. This has robbed me of my will to live.”</p></blockquote>
<p><em>No developer, anywhere, ever.</em></p>
<blockquote><p>“Ugh, I have to write tests. This has robbed me of my will to live.”</p></blockquote>
<p><em>All developers, on a daily basis.</em></p>
<p>Why do we dread writing tests so much?</p>
<p>A lot of developers approach tests as an after-the-fact thing: the code is done, it’s time to write tests.</p>
<p>But really, it should be: the code is done now that I’ve written my tests.</p>
<p>The virtue of TDD is that you <em>have</em> to write tests. You can’t write code unless you’ve written tests. This one factor has contributed largely to its success. I’m not advocating for or against TDD, I’m lobbying for you to consider your tests as an essential part of your code.</p>
<p>If anything, a well-written test just proves that you’re right, and who doesn’t love being right?</p>
<div class="about-box">
<a href="http://feministy.io"><img src="https://www.gravatar.com/avatar/c79fa237a028944ad06a4b0a50925120.png" class="avatar" align="left" /></a>
<p>
<a href="http://feministy.io" class="author-name" target="_blank">Liz Abinante</a> is an engineer for Instructure and co-leader of the <a href="http://www.meetup.com/Girl-Develop-It-Chicago-IL/" target="_blank">Girl Develop It Chicago chapter</a>. She is infectiously enthusiastic about web development, teaching, learning, and tacos. She enjoys writing and <a href="https://speakerdeck.com/feministy/" target="_blank">speaking</a> about education, diversity, and happiness in engineering. Previously, Liz has worked as a writer, editor, and knitwear designer.
</p>
</div>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Ember Tech Talks - April 2014]]></title>
<link href="http://instructure.github.io/blog/2014/04/28/ember-tech-talks-april-2014/"/>
<updated>2014-04-28T09:19:00-05:00</updated>
<id>http://instructure.github.io/blog/2014/04/28/ember-tech-talks-april-2014</id>
<content type="html"><![CDATA[<p>Each month, geeks from near and far join up at Instructure to teach and learn
about <a href="http://emberjs.com">Ember.js</a>. This month we had 3 amazing presentations
which range from beginner to expert level training.</p>
<h2>Ember Functions, Computed Properties, Observers and More by <a href="https://twitter.com/DericAbel">Deric Abel</a></h2>
<iframe width="586" height="440" src="http://instructure.github.io//www.youtube.com/embed/cIJCTd8qJcQ?rel=0" frameborder="0" allowfullscreen></iframe>
<h2>Binding to Twitter Bootstrap with Ember by <a href="https://github.com/brettv">Brett Valentine</a></h2>
<iframe width="586" height="440" src="http://instructure.github.io//www.youtube.com/embed/Z3-t4tF1OPA?rel=0" frameborder="0" allowfullscreen></iframe>
<p><a href="https://github.com/brettv/ember---bootstrap">Slides</a></p>
<h2>répondez, s’il vous plaît – a deep dive into Ember Promise/RSVP by <a href="https://github.com/kingpin2k">Daniel Dunkley</a></h2>
<iframe width="586" height="440" src="http://instructure.github.io//www.youtube.com/embed/8WXgm4_V85E?rel=0" frameborder="0" allowfullscreen></iframe>
<p><a href="https://docs.google.com/presentation/d/1DYjd2BzCrj-no2V2NsqqEfsYlliUEWPf8hCtVVkX0lM/pub?start=false&loop=false&delayms=3000">Slides</a></p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[ImageSweep : drawable management for Android]]></title>
<link href="http://instructure.github.io/blog/2014/04/21/android-image-sweep/"/>
<updated>2014-04-21T07:29:00-05:00</updated>
<id>http://instructure.github.io/blog/2014/04/21/android-image-sweep</id>
<content type="html"><![CDATA[<p>Software engineering is full of trade-offs: speed/memory usage, features/bugs, etc.
The list goes on and on. The Android team here at Instructure found an interesting trade-off: developer-time versus application size.
As we get new icons/images from our UI/UX team, we have two choices: bundle all of them into the application or add them as they’re needed in our project.
The trade-off becomes relatively straight-forward: either we spend time constantly adding images to our project or the user has to deal with their size when they install our application.</p>
<p>At a high-level, the solution is simple: find all the unused drawables in the project and remove them when you build for release.
Historically that’s been a tedious and error-prone process, but not anymore.
I’m proud to announce a new open-source script from Instructure: <a href="https://github.com/instructure/android-ImageSweep">ImageSweep</a>.
It will run through every file in your project and check for references to drawable resources using regular expressions to check for instances of <em>R.drawable.</em> and <em>@drawable/</em>.
The script will then iterate through the resource folder and delete ALL unused drawable resources.</p>
<p>It’s extremely easy to use. Simply run:</p>
<pre><code> python ImageSweep.py project_src_directory
</code></pre>
<p>where <code>project_src_directory</code> is the relative or absolute file-path where your source code lives.
Make sure the chosen directory contains <em>all</em> of your source code and AndroidManifest.xml, but none of the libraries you’ve included. The script auto-determines where the project’s resource folder is and libraries can potentially break this detection.</p>
<p>We recently added this script to our release process for <a href="https://play.google.com/store/apps/details?id=com.instructure.candroid">Canvas for Android</a>.
In that project, we were able to delete 2,593 files for a total of 72.37 Mbs freed on disk.
This correlated to a <em>46%</em> size decrease on the apk itself and <em>26%</em> size decrease on the installed application.</p>
<p>To get started, visit the <a href="https://github.com/instructure/android-ImageSweep">project homepage</a>. Take note of the warnings in the README prior to using the script.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Continuous Integration]]></title>
<link href="http://instructure.github.io/blog/2014/02/24/ci/"/>
<updated>2014-02-24T11:04:00-06:00</updated>
<id>http://instructure.github.io/blog/2014/02/24/ci</id>
<content type="html"><![CDATA[<p>Parallelization, Selenium, AWS, and a CI server are common implementations for agile
shops, and we are no exception. Since I’ve been here, maintenance hasn’t
been a nightmare, nor has it been a dream. We’ve learned a few things
to streamline our maintenance process and would love to share the things
that made our lives easier.</p>
<p>When looking at what was eating up our time, we found the differences
between our CI environment and average developer’s environment was at
the forefront. Most of us at Instructure use Macs, a few of us Windows,
and there are a handful Linux users. Among us, browser choice varies,
but the most popular is Chrome. Engineers develop locally using their
own OS, browser type/version and the Ruby Selenium driver to power
tests. Listed below are a few of the differences we found:</p>
<ol>
<li> AWS images were Ubuntu</li>
<li> Variance in browser versions</li>
<li> Selenium grid uses Java stand alone server VS local specs running using native Ruby driver</li>
<li> Variance in native event capabilities VS Jenkins running exclusively using native events</li>
<li> Parallelization in CI vs single threaded specs in developer’s environment</li>
</ol>
<!-- more -->
<p>We began to strive for consistency between the average dev environment and
the CI environments, where we could, without sacraficing production like implementation.
This helped with mitigating many intermittency issues. During this
process, it simplified the CI upgrade process while edging maintenance
towards dreamland and further from Elm St.</p>
<p>So, what did we do to make this happen? First, all logic for Selenium
driver code was changed from Selenium grid\Java stand-alone server to
the native Ruby Selenium driver. This change immediately resulted in
noticeable stability improvements, and easier resolution for
intermittency creep from tests.</p>
<p>In the past, we had challenges reproducing errors, locally, that our CI
server would throw because of the variance in environments. We spent a
lot of time debugging these errors, and had to create CI environments
that devs could SSH into, to debug the spec failures. This was highly
inefficient, and not consistent with the nature or true purpose of CI.
Making the two environments consistent gave us more time to work on
other things, instead of time-consuming and meaningless failures that were challenging and
intensive to debug. Now we are easily able to (for the most part)
reproduce all errors produced by our CI server.</p>
<p>Along the way, we also found Selenium grid was not the ideal solution for us.
The main reason being the variance in spec outcomes
between dev environments and CI, resulting in too much overhead for
maintenance and upgrades, while also sapping dev time on CI spec
debugging. Many shops use a hardware infrastructure that is distributed
across multiple boxes and the tests are farmed out to them; this is
ideal for Selenium grid. We, however, prefer one big box with
parallelization done internally, reducing the need for a Java stand-alone
server and Selenium grid. Capabilities for the drivers are consumed from
config files written at runtime for different browsers and we have no
need to push each test to a Selenium hub to be farmed out to a worker.
We found this complicated setup offered little reward and brought on
noticeable burden in maintenance and stability while introducing a
variance in spec outcomes.</p>
<p>Making these changes made our lives noticeably easier. When managing
or implementing a CI environment, we believe it’s prudent to not only consider your core
tech architecture, but the relationship with your developer’s workflow.
Although complex implementations of Selenium grid and multiple browsers can be beneficial,
it may add more complications than are worth it.
We recommend a lean implementation of Selenium, using the driver of the native application
language for tighter integration and better maintenance with more consistency between test runs.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Ember Run Loop and TDD]]></title>
<link href="http://instructure.github.io/blog/2014/01/24/ember-run-loop-and-tdd/"/>
<updated>2014-01-24T08:58:00-06:00</updated>
<id>http://instructure.github.io/blog/2014/01/24/ember-run-loop-and-tdd</id>
<content type="html"><![CDATA[<p>Each month, <a href="http://www.meetup.com/EmberJS-SLC/">nerds unite</a> at the Instructure building to share their knowledge on <a href="http://www.emberjs.com">Ember</a>. This month we had two presentations by a couple of Instructure engineers. Here are the videos and accompanying slides/code.</p>
<h2>Ember Run Loop</h2>
<p>Jason Madsen (<a href="https://twitter.com/jason_madsen">@jason_madsen</a>) discussed the what, why, and how of the Ember Run Loop. Learn how Ember manages the run loop for you, and cases where you will want to manage the run loop. Here’s a hint: speed and testing.</p>
<iframe width="560" height="315" src="http://instructure.github.io//www.youtube.com/embed/RLgPBM72LQw" frameborder="0" allowfullscreen></iframe>
<p><a href="http://knomedia.github.io/presentation_2014_01_23_ember_run_loop">Slides</a></p>
<h2>Testing Your Ember Application using TDD</h2>
<p>Eric Berry (<a href="https://twitter.com/cavneb">@cavneb</a>) presented on how to “Test Drive” your Ember applications. Eric starts from scratch and walks through different types of tests that can be written. By the end, you will have a much better understanding of how to test your Ember application.</p>
<iframe width="560" height="315" src="http://instructure.github.io//www.youtube.com/embed/GRT5YcXmm7E" frameborder="0" allowfullscreen></iframe>
<p><a href="http://cavneb.github.io/presentations-2014.01.23-Ember-Testing">Slides</a> |
<a href="https://github.com/cavneb/presentations-2014.01.23-Ember-Testing/tree/master/ember-app">Code</a></p>
<h2>Nerd with us!</h2>
<p>Feel free to join our monthly meetups (even remotely). The meetings are posted at <a href="http://www.meetup.com/EmberJS-SLC/">http://www.meetup.com/EmberJS-SLC/</a>.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Faster I18n Backend for Ruby Written in C]]></title>
<link href="http://instructure.github.io/blog/2014/01/07/faster-ruby-i18n-backend-written-in-c/"/>
<updated>2014-01-07T07:32:00-06:00</updated>
<id>http://instructure.github.io/blog/2014/01/07/faster-ruby-i18n-backend-written-in-c</id>
<content type="html"><![CDATA[<p>Every so often, we like to do a hack(fest|stravaganza|nado) here at
Instructure. It’s an opportunity for engineers to spend several days
building something cool to improve <a href="https://github.com/instructure/canvas-lms">Canvas</a>.</p>
<p>For our inaugural hackthing back in May, I worked on the problem of having
lots of objects in memory in Ruby 1.9. Ruby uses a mark-and-sweep garbage
collector, so the more Ruby objects you have in memory, the longer each GC
run will take. This can be a significant contributor to slow page loads in
a large Rails app like Canvas.</p>
<!--more-->
<p>At the time, Canvas was localized in 7 languages (we’re now up to 15). We
use the excellent <a href="https://github.com/svenfuchs/i18n">I18n gem</a> with a
<a href="https://github.com/instructure/canvas-lms/blob/stable/config/initializers/i18n.rb">few</a>
<a href="https://github.com/instructure/canvas-lms/blob/stable/lib/tasks/i18n.rake">handy</a>
<a href="https://github.com/instructure/canvas-lms/tree/stable/lib/i18n_extraction">extensions</a>
of our own. While I18n’s default in-memory backend is plenty fast, it
comes at the cost of having all of those strings in memory. Every new
language you add or feature you implement makes the GC problem even worse.</p>
<p>Because localization strings should be static for the lifetime of a Rails
process, there is no reason the garbage collector needs to know about them
at all. But we still want to keep them in memory for optimal speed.</p>
<p>Enter <a href="https://github.com/instructure/i18nema">I18nema</a>, a fast I18n
backend that unstops the garbage collector and gets everything running
quickly and smoothly :).</p>
<h2>What Is an I18nema?</h2>
<p>At its core, I18nema is a Ruby C extension that moves I18n’s translations
out of Ruby-land and into C structs. It also includes a handful of other
optimizations to I18n, leading to some nice all around speedups.</p>
<h2>How Is It Administered?</h2>
<p>In your Gemfile, do</p>
<figure class='code'><figcaption><span></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='ruby'><span class='line'><span class="n">gem</span> <span class="no">RUBY_VERSION</span> <span class="o">>=</span> <span class="s1">'2.0'</span> <span class="p">?</span> <span class="s1">'i18nema'</span> <span class="p">:</span> <span class="s1">'i18nema19'</span>
</span></code></pre></td></tr></table></div></figure>
<p>and then put something like this in an initializer:</p>
<figure class='code'><figcaption><span></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='ruby'><span class='line'><span class="no">I18n</span><span class="o">.</span><span class="n">backend</span> <span class="o">=</span> <span class="ss">I18nema</span><span class="p">:</span><span class="ss">:Backend</span><span class="o">.</span><span class="n">new</span>
</span></code></pre></td></tr></table></div></figure>
<p>You can still pull in additional I18n features (e.g. <code>I18n::Backend::Fallbacks</code>).
Refer to <a href="https://github.com/instructure/i18nema/blob/master/README.md">the README</a>
for more information.</p>
<h2>How Will It Improve My Quality of Life?</h2>
<h3>More Get-Up-and-Go</h3>
<p>I18nema loads translations into memory much more quickly—<strong>over 4x!</strong>—
making for much faster Rails startup time. While this is just a one time
hit, it’s very noticeable when you’re waiting on it (e.g. console, specs).
In Canvas, I18nema brings it down to a little over a second (from almost
6).</p>
<h3>Minimal Blockage</h3>
<p>Because there are fewer Ruby objects, the periodic GC runs are
proportionally faster with I18nema. That means faster page loads for your
users.</p>
<p>How much faster is a question of how many translations you have versus how
many other Ruby objects. Applications that are localized in more languages
should see a bigger boost (since the translations make up a bigger share
of the original ObjectSpace).</p>
<p>For example, Canvas is translated into fifteen languages, and I18nema
reduces both (startup) ObjectSpace and GC runtime by <strong>about 15%</strong>. As more
languages are added, that number should only increase.</p>
<p>I18nema also moves I18n’s normalized_key_cache into C structs. This key
cache grows over time (it eventually holds a key/value for every
translation key used in the app), so that’s another area where I18nema is
nicer on ObjectSpace than vanilla I18n.</p>
<h3>More Pep in Your Step</h3>
<p>I18nema speeds up <code>translate</code> calls, getting the right text to your users
more quickly.</p>
<p>Simple lookups (i.e. no options or interpolation) take a bit <strong>over 15%</strong> less
time.</p>
<p>Lookups with options see slightly bigger gains (<strong>over 20%</strong> less time), in
part due to some speedups on the Ruby side of things (I18n uses
<code>Hash#except</code>, which is quite slow when you have a long list of
arguments).</p>
<h2>Is I18nema Right for Me?</h2>
<p>I18nema is not for everyone.</p>
<p>If you’re still on Ruby 1.8, you should not use I18nema. Although I18nema
has been tested on Ruby 1.9, 2.0, and 2.1, it has only been benchmarked
on 1.9. You may find it less effective on 2.1, due to its <a href="http://tmm1.net/ruby21-rgengc/">dramatic GC improvements</a>.</p>
<p>When using I18nema, you should make sure that your translation files are
UTF-8. I18nema cannot be used with <code>.rb</code> translation files (only <code>.yml</code>)</p>
<p>Refer to <a href="https://github.com/instructure/i18nema/blob/master/README.md">the fine print</a>
for more information and benchmarks.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Generating Accessible Colors in JavaScript]]></title>
<link href="http://instructure.github.io/blog/2013/12/30/generating-accessible-colors-in-javascript/"/>
<updated>2013-12-30T14:42:00-06:00</updated>
<id>http://instructure.github.io/blog/2013/12/30/generating-accessible-colors-in-javascript</id>
<content type="html"><![CDATA[<p>We recently released a major upgrade to Canvas’s calendar. Although the primary objective was adding an agenda view and other accessibility tools, we also took the opportunity to update its visual design, including the color coding that links events and calendars. We’ve released this new color code generator as a Bower package at <a href="https://github.com/instructure/color-slicer">github/instructure/color-slicer</a>. Here’s why we switched.</p>
<!--more-->
<h2>Objectives</h2>
<p>Our old color coding system was pretty simple: we had a list of ten hues, and we assigned them to calendars in order. The drawbacks were that colors would repeat after ten calendars and that the colors used weren’t very appealing.</p>
<p> <img src="http://i.imgur.com/6lBEr3V.png" alt="" /></p>
<p>Another system we considered was <a href="https://github.com/instructure/canvas-lms/blob/stable/app/coffeescripts/util/contextColors.coffee">our hash-based color generator</a>, which creates a color based on an object name or ID. This would avoid repetition, but unlucky users could end up with three slightly different shades of green for their three courses.</p>
<p>For the new system, then, we had three goals:</p>
<ul>
<li>Produce colors that look nice and are accessibly readable for text.</li>
<li>Allow an unlimited number of colors without repetition.</li>
<li>Keep colors visually distinct, especially for short lists.</li>
</ul>
<h2>Text colors</h2>
<p>The simplest way to generate bright colors would be use HSV and to vary the hue while using 100% saturation and value. For text on a white background, though, this makes yellows almost invisible:</p>
<p><a href="http://jsfiddle.net/3KX8d/1/"><img src="http://i.imgur.com/kk4ntIE.png" /></a></p>
<p>Instead, we use the <a href="http://en.wikipedia.org/wiki/Lab_color_space">Lab color space</a>, which adjusts for human sensitivity to green, providing a better measurement of lightness and more evenly distributed hues. This not only looks better, but also helps users with limited vision by meeting <a href="http://webaim.org/resources/contrastchecker/">WCAG AA contrast guidelines</a>.</p>
<p><a href="http://jsfiddle.net/G9Qmm/1/"><img src="http://i.imgur.com/V5jstLt.png?1" /></a></p>
<h2>Spacing</h2>
<p>An easy way to space the colors would be to divide the hue circle by the number of calendars. We didn’t want all of the colors to change whenever a user added a course, though; we wanted to use the same colors for the first three calendars whether there were three or thirty total. To accomplish this, we <a href="http://jsfiddle.net/UqSS3/6/">progressively divide each gap in half</a>: first we use 0°, then 180°, then 90°, then 270°, then 45°, and so on. This way, your first few calendars are always very visually distinct.</p>
<p>Formally, this is essentially an <a href="http://en.wikipedia.org/wiki/Online_algorithm">online</a> <a href="http://en.wikipedia.org/wiki/Thomson_problem">Thomson problem</a>. We’re lucky to only have to solve the one-dimensional version, but the same basic idea could certainly be used to vary hue and value simultaneously if you wanted to generate many distinct colors and didn’t care about legibility. The <a href="http://sitemason.vanderbilt.edu/page/hmbADS">spiral point heuristic</a> would probably work well—we’d love to see someone send us a pull request.</p>
<h2>Results</h2>
<p>We’re always refining our user experience, but we’re happy with the look of our new colors.</p>
<p> <img src="http://i.imgur.com/GLSrwyh.png?1" alt="" /></p>
<p>We’ve released <a href="https://github.com/instructure/color-slicer">color-slicer</a> in UMD format under an MIT license, so it should be easy to add to your projects. I hope you’ll find it useful. My favorite part of working at Instructure—better than weekly lunch—is spending most of my time crafting code that we give back to the community. Let us know what you think.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Facebook React vs. Ember]]></title>
<link href="http://instructure.github.io/blog/2013/12/17/facebook-react-vs-ember/"/>
<updated>2013-12-17T08:54:00-06:00</updated>
<id>http://instructure.github.io/blog/2013/12/17/facebook-react-vs-ember</id>
<content type="html"><![CDATA[<div class="comment">Edited Jan 13, 2014 with minor changes which allow for testing timeouts in Ember.</div>
<p>We engineers at Instructure love <a href="http://www.emberjs.com">Ember</a>. We also love to learn about other frameworks and know which tools are the best for the job at hand. One of the newer frameworks to come out is React.</p>
<p><a href="http://facebook.github.io/react/">React</a> is Facebook’s new JavaScript library for building user interfaces. I’m excited to see another player in the game of front-end JS frameworks. When we have so much mind share going to a similar problem, we all win.</p>
<p>On the home page <a href="http://instructure.github.io/images/posts/react-website.png">(screenshot)</a>, there are a few examples of how React can be used. I thought it might be fun to show how each of these can be done using Ember.</p>
<!-- more -->
<h2>A Simple Component</h2>
<p>React components take data and return content which is to be displayed. Here’s their example code:</p>
<figure class='code'><figcaption><span>React</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>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="cm">/** @jsx React.DOM */</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">HelloMessage</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createClass</span><span class="p">({</span>
</span><span class='line'> <span class="nx">render</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">return</span> <span class="o"><</span><span class="nx">div</span><span class="o">></span><span class="p">{</span><span class="s1">'Hello '</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="o"><</span><span class="err">/div>;</span>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'><span class="p">});</span>
</span><span class='line'>
</span><span class='line'><span class="nx">React</span><span class="p">.</span><span class="nx">renderComponent</span><span class="p">(</span><span class="o"><</span><span class="nx">HelloMessage</span> <span class="nx">name</span><span class="o">=</span><span class="s2">"John"</span> <span class="o">/></span><span class="p">,</span> <span class="nx">mountNode</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>
<p>Let’s build this in Ember. For something as simple as this example, a <a href="http://emberjs.com/guides/templates/writing-helpers/">Handlebars Helper</a> will do the trick.</p>
<figure class='code'><figcaption><span>Ember</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>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">Ember</span><span class="p">.</span><span class="nx">Handlebars</span><span class="p">.</span><span class="nx">helper</span><span class="p">(</span><span class="s1">'hello-message'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'> <span class="kd">var</span> <span class="nx">escaped</span> <span class="o">=</span> <span class="nx">Handlebars</span><span class="p">.</span><span class="nx">Utils</span><span class="p">.</span><span class="nx">escapeExpression</span><span class="p">(</span><span class="nx">name</span><span class="p">);</span>
</span><span class='line'> <span class="k">return</span> <span class="k">new</span> <span class="nx">Handlebars</span><span class="p">.</span><span class="nx">SafeString</span><span class="p">(</span><span class="s1">'<div>Hello '</span> <span class="o">+</span> <span class="nx">escaped</span> <span class="o">+</span> <span class="s1">'</div>'</span><span class="p">);</span>
</span><span class='line'><span class="p">});</span>
</span><span class='line'>
</span><span class='line'><span class="p">{{{</span><span class="nx">hello</span><span class="o">-</span><span class="nx">message</span> <span class="s2">"John"</span><span class="p">}}}</span>
</span></code></pre></td></tr></table></div></figure>
<h3>Example:</h3>
<p><a class="jsbin-embed" href="http://jsbin.com/ecAJuxE/4/edit?html,js,output">Facebook React vs Ember</a><script src="http://static.jsbin.com/js/embed.js"></script></p>
<h2>A Stateful Component</h2>
<p>The stateful component example on the React page is cool. It tracks the elapsed time on a page (since refresh). Here’s their code:</p>
<figure class='code'><figcaption><span>React</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>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">var</span> <span class="nx">Timer</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createClass</span><span class="p">({</span>
</span><span class='line'> <span class="nx">getInitialState</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">return</span> <span class="p">{</span><span class="nx">secondsElapsed</span><span class="o">:</span> <span class="mi">0</span><span class="p">};</span>
</span><span class='line'> <span class="p">},</span>
</span><span class='line'> <span class="nx">tick</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">secondsElapsed</span><span class="o">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">secondsElapsed</span> <span class="o">+</span> <span class="mi">1</span><span class="p">});</span>
</span><span class='line'> <span class="p">},</span>
</span><span class='line'> <span class="nx">componentDidMount</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">this</span><span class="p">.</span><span class="nx">interval</span> <span class="o">=</span> <span class="nx">setInterval</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">tick</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span>
</span><span class='line'> <span class="p">},</span>
</span><span class='line'> <span class="nx">componentWillUnmount</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="nx">clearInterval</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">interval</span><span class="p">);</span>
</span><span class='line'> <span class="p">},</span>
</span><span class='line'> <span class="nx">render</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">return</span> <span class="nx">React</span><span class="p">.</span><span class="nx">DOM</span><span class="p">.</span><span class="nx">div</span><span class="p">({},</span>
</span><span class='line'> <span class="s1">'Seconds Elapsed: '</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">secondsElapsed</span>
</span><span class='line'> <span class="p">);</span>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'><span class="p">});</span>
</span></code></pre></td></tr></table></div></figure>
<p>Since we are maintaining state within the component itself, let’s build it using an Ember Component.</p>
<figure class='code'><figcaption><span>Ember</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>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">App</span><span class="p">.</span><span class="nx">TimeOnPageComponent</span> <span class="o">=</span> <span class="nx">Em</span><span class="p">.</span><span class="nx">Component</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
</span><span class='line'> <span class="nx">secondsViewed</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span><span class='line'>
</span><span class='line'> <span class="c1">// Increment the secondsViewed attribute</span>
</span><span class='line'> <span class="nx">tick</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">this</span><span class="p">.</span><span class="nx">incrementProperty</span><span class="p">(</span><span class="s1">'secondsViewed'</span><span class="p">);</span>
</span><span class='line'> <span class="p">},</span>
</span><span class='line'>
</span><span class='line'> <span class="nx">onInterval</span><span class="o">:</span> <span class="kd">function</span><span class="p">(){</span>
</span><span class='line'> <span class="nx">Ember</span><span class="p">.</span><span class="nx">run</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">tick</span><span class="p">);</span>
</span><span class='line'> <span class="p">},</span>
</span><span class='line'>
</span><span class='line'> <span class="c1">// Initiates timer when element is rendered</span>
</span><span class='line'> <span class="nx">startTimer</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">this</span><span class="p">.</span><span class="nx">_interval</span> <span class="o">=</span> <span class="nx">setInterval</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">onInterval</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">),</span> <span class="mi">1000</span><span class="p">);</span>
</span><span class='line'> <span class="p">}.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'didInsertElement'</span><span class="p">),</span>
</span><span class='line'>
</span><span class='line'> <span class="c1">// Ensure that the timer stops when closed</span>
</span><span class='line'> <span class="nx">clearInterval</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="nx">clearInterval</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_interval</span><span class="p">);</span>
</span><span class='line'> <span class="k">this</span><span class="p">.</span><span class="nx">_interval</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
</span><span class='line'> <span class="p">}.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'willDestroyElement'</span><span class="p">)</span>
</span><span class='line'><span class="p">});</span>
</span></code></pre></td></tr></table></div></figure>
<h3>Example:</h3>
<p><a class="jsbin-embed" href="http://jsbin.com/OPeVeted/6/embed?js,output">Facebook React vs Ember</a><script src="http://static.jsbin.com/js/embed.js"></script></p>
<h2>An Application</h2>
<p>The application example is a simple todo list which has a TodoList class (for rendering content) and a TodoApp class (maintains the state and renders the layout). Here’s the code:</p>
<figure class='code'><figcaption><span>React</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>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="cm">/** @jsx React.DOM */</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">TodoList</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createClass</span><span class="p">({</span>
</span><span class='line'> <span class="nx">render</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="kd">var</span> <span class="nx">createItem</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">itemText</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'> <span class="k">return</span> <span class="o"><</span><span class="nx">li</span><span class="o">></span><span class="p">{</span><span class="nx">itemText</span><span class="p">}</span><span class="o"><</span><span class="err">/li>;</span>
</span><span class='line'> <span class="p">};</span>
</span><span class='line'> <span class="k">return</span> <span class="o"><</span><span class="nx">ul</span><span class="o">></span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">createItem</span><span class="p">)}</span><span class="o"><</span><span class="err">/ul>;</span>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'><span class="p">});</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">TodoApp</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createClass</span><span class="p">({</span>
</span><span class='line'> <span class="nx">getInitialState</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">return</span> <span class="p">{</span><span class="nx">items</span><span class="o">:</span> <span class="p">[],</span> <span class="nx">text</span><span class="o">:</span> <span class="s1">''</span><span class="p">};</span>
</span><span class='line'> <span class="p">},</span>
</span><span class='line'> <span class="nx">onChange</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'> <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">text</span><span class="o">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">});</span>
</span><span class='line'> <span class="p">},</span>
</span><span class='line'> <span class="nx">handleSubmit</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'> <span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
</span><span class='line'> <span class="kd">var</span> <span class="nx">nextItems</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">concat</span><span class="p">([</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">text</span><span class="p">]);</span>
</span><span class='line'> <span class="kd">var</span> <span class="nx">nextText</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span>
</span><span class='line'> <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">items</span><span class="o">:</span> <span class="nx">nextItems</span><span class="p">,</span> <span class="nx">text</span><span class="o">:</span> <span class="nx">nextText</span><span class="p">});</span>
</span><span class='line'> <span class="p">},</span>
</span><span class='line'> <span class="nx">render</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">return</span> <span class="p">(</span>
</span><span class='line'> <span class="o"><</span><span class="nx">div</span><span class="o">></span>
</span><span class='line'> <span class="o"><</span><span class="nx">h3</span><span class="o">></span><span class="nx">TODO</span><span class="o"><</span><span class="err">/h3></span>
</span><span class='line'> <span class="o"><</span><span class="nx">TodoList</span> <span class="nx">items</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">items</span><span class="p">}</span> <span class="o">/></span>
</span><span class='line'> <span class="o"><</span><span class="nx">form</span> <span class="nx">onSubmit</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">handleSubmit</span><span class="p">}</span><span class="o">></span>
</span><span class='line'> <span class="o"><</span><span class="nx">input</span> <span class="nx">onChange</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">onChange</span><span class="p">}</span> <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">text</span><span class="p">}</span> <span class="o">/></span>
</span><span class='line'> <span class="o"><</span><span class="nx">button</span><span class="o">></span><span class="p">{</span><span class="s1">'Add #'</span> <span class="o">+</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)}</span><span class="o"><</span><span class="err">/button></span>
</span><span class='line'> <span class="o"><</span><span class="err">/form></span>
</span><span class='line'> <span class="o"><</span><span class="err">/div></span>
</span><span class='line'> <span class="p">);</span>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'><span class="p">});</span>
</span><span class='line'><span class="nx">React</span><span class="p">.</span><span class="nx">renderComponent</span><span class="p">(</span><span class="o"><</span><span class="nx">TodoApp</span> <span class="o">/></span><span class="p">,</span> <span class="nx">mountNode</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>
<p>When writing this in Ember, we would use both a controller and a handlebars template. The controller will maintain the state and respond to user interaction and the template will display the form and list.</p>
<figure class='code'><figcaption><span>Ember (controller)</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>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">App</span><span class="p">.</span><span class="nx">ApplicationController</span> <span class="o">=</span> <span class="nx">Em</span><span class="p">.</span><span class="nx">ArrayController</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
</span><span class='line'> <span class="nx">content</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">return</span> <span class="p">[];</span>
</span><span class='line'> <span class="p">}.</span><span class="nx">property</span><span class="p">(),</span>
</span><span class='line'>
</span><span class='line'> <span class="nx">btnLabel</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">return</span> <span class="s2">"Add #"</span> <span class="o">+</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'content.length'</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
</span><span class='line'> <span class="p">}.</span><span class="nx">property</span><span class="p">(</span><span class="s1">'content.length'</span><span class="p">),</span>
</span><span class='line'>
</span><span class='line'> <span class="nx">actions</span><span class="o">:</span> <span class="p">{</span>
</span><span class='line'> <span class="nx">handleSubmit</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'> <span class="k">this</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'content'</span><span class="p">).</span><span class="nx">pushObject</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'text'</span><span class="p">));</span>
</span><span class='line'> <span class="k">this</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="s1">'text'</span><span class="p">,</span> <span class="s1">''</span><span class="p">);</span>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'><span class="p">});</span>
</span></code></pre></td></tr></table></div></figure>
<figure class='code'><figcaption><span>Ember (template)</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>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt"><div></span>
</span><span class='line'> <span class="nt"><h3></span>TODO<span class="nt"></h3></span>
</span><span class='line'> <span class="nt"><ul></span>
</span><span class='line'> {{#each content}}
</span><span class='line'> <span class="nt"><li></span>{{this}}<span class="nt"></li></span>
</span><span class='line'> {{/each}}
</span><span class='line'> <span class="nt"></ul></span>
</span><span class='line'>