-
Notifications
You must be signed in to change notification settings - Fork 1
/
presentation.html
246 lines (246 loc) · 17.4 KB
/
presentation.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
<!DOCTYPE html>
<html class='no-js'>
<head>
<title>Client Side MVC with Backbone.js</title>
<meta content='text/html; charset=utf-8' http-equiv='Content-type' />
<script type='text/javascript'>
//<![CDATA[
(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement)
//]]>
</script>
<link href='stylesheets/shCoreMidnight.css' media='Screen,projection' rel='stylesheet' type='text/css' />
<link href='stylesheets/screen.css' media='Screen,projection' rel='stylesheet' type='text/css' />
<script src='lib/jquery/jquery-1.6.4.min.js' type='text/javascript'></script>
<script src='scripts/jquery.presentation.js' type='text/javascript'></script>
<script src='scripts/global.js' type='text/javascript'></script>
<script src='scripts/shCore.js' type='text/javascript'></script>
<script src='scripts/shBrushJScript.js' type='text/javascript'></script>
<script src='scripts/shBrushCoffee.js' type='text/javascript'></script>
<script src='lib/underscore/underscore-min.js' type='text/javascript'></script>
<script src='lib/modernizr/modernizr-1.6.min.js' type='text/javascript'></script>
<script src='lib/backbone/backbone-min.js' type='text/javascript'></script>
<script src='scripts/backbone.localStorage.js' type='text/javascript'></script>
<script src='scripts/example-model.js' type='text/javascript'></script>
<script src='scripts/example-view.js' type='text/javascript'></script>
<script src='scripts/example-collection.js' type='text/javascript'></script>
<script id='timer-template' type='text/template'>
<%- name %> in <%- time %> <%- unit %>
</script>
<script id='new-timer-template' type='text/template'>
<form>
<input class=name type=text value='<%- name %>' />
<span class='method'>in</span>
<input class=time type=text value='<%- time %>' />
<input class=unit type=text value='<%- unit %>' />
<input class='submit' name='save-timer' type='button' value='create' />
</form>
</script>
</head>
<body>
<div id='header'>
<div class='container'>
<span class='title'>Client Side MVC with Backbone.js</span>
</div>
</div>
<div id='content'>
<div id='slides'>
<div class='slide'>
<h2>Introduction</h2>
<p>Backbone is a leight-weight and clean JS library (~1000 LOC) used by projects such as</p>
<figure>
<img src='images/examples/linkedin-mobile.png' />
<figcaption>LinkedIn Mobile</figcaption>
</figure>
<figure>
<img src='images/examples/groupon-now.png' />
<figcaption>Groupon Now!</figcaption>
</figure>
<figure>
<img src='images/examples/basecamp-mobile.png' />
<figcaption>Basecamp Mobile</figcaption>
</figure>
<figure>
<img src='images/examples/soundcloud-mobile.png' />
<figcaption>SoundCloud Mobile</figcaption>
</figure>
<figure>
<img src='images/examples/grove.io.png' />
<figcaption>Grove.io</figcaption>
</figure>
<figure>
<img src='images/examples/bittorrent.png' />
<figcaption>BitTorrent</figcaption>
</figure>
</div>
<div class='slide'>
<h2>Part I</h2>
<ol>
<li>Why Backbone.js?</li>
<li>What can Backbone do?</li>
<li>What can't Backbone do?</li>
<li>Analogies to Ruby on Rails</li>
<li>Models</li>
<li>Views</li>
<li>Routers</li>
<li>Collections</li>
<li>Backend & Collection persistency</li>
</ol>
</div>
<div class='slide'>
<h2>1. Why Backbone.js?</h2>
<p>Implementing non-trivial JS logic tends to be</p>
<ul>
<li>difficult to manage, at a certain point</li>
<li>ugly (jQuery-chains, typeless classes)</li>
<li>DOM-oriented (low abstraction)</li>
<li>lacking DRYness</li>
<li>storing data in the DOM</li>
</ul>
</div>
<div class='slide'>
<h2>2. What can Backbone do?</h2>
<p>Allows to structure your JS code MVC style:</p>
<pre class='brush: coffee'>class App.Views.Post extends Backbone.View
class App.Model.Post extends Backbone.Model
class App.Collections.Posts extends Backbone.Collection
class App.Routers.PostsRouters extends Backbone.Router</pre>
<p>Yields semantically meaningful JS classes.</p>
<p>Offers powerful event bindings.</p>
</div>
<div class='slide'>
<h2>3. What can't Backbone do?</h2>
<p>Not self-contained, requires jQuery and Underscore.</p>
<p>No native templating except Underscore.</p>
<p>No native local storage implementation.</p>
</div>
<div class='slide'>
<h2>4. Analogies to Ruby on Rails</h2>
<table>
<thead>
<tr>
<th>Backbone.js</th>
<th>Ruby on Rails</th>
</tr>
</thead>
<tr>
<td>Backbone.Model</td>
<td>ActiveModel</td>
</tr>
<tr>
<td>Backbone.View</td>
<td>ActionView</td>
</tr>
<tr>
<td>Backbone.Router</td>
<td>ActionController / ActionDispatch</td>
</tr>
<tr>
<td>Backbone.Collection</td>
<td>ActiveRecord</td>
</tr>
</table>
</div>
<div class='slide'>
<h2>5. Models</h2>
<p>Core of any Backbone.js application, handles its properties and logic. Can provide validations, access control and conversions.</p>
<p>It's sufficient to extend Backbone's Model class:</p>
<pre class='brush: coffee'>class window.App.Models.Timer extends Backbone.Model
</pre>
<p>You can implement additional behaviour, if required:</p>
<pre class='brush: coffee'> class window.Timer extends Backbone.Model

 initialize: ->
 @reminders = new Backbone.Collection()

 defaults:
 unit: 's'

 validate: (attr) ->
 if ((new Date() - attr.end) > 0)
 return "Timer can't be in the past"</pre>
<pre class='brush: js'>var m = new window.Timer({ name: 'foo'});</pre>
</div>
<div class='slide'>
<h2>6. Views</h2>
<p>Views represent the DOM side of the application and handle rendering of model data.</p>
<pre class='brush: coffee'>class window.TimerView extends Backbone.View

 tagName: 'li'

 initialize: ->
 _.bindAll this, 'render'
 @template = _.template $('#timer-template').html()
 @model.bind 'change', @render

 render: ->
 $(@el).html @template(@model.toJSON())</pre>
<ul class='place-holder'>
<li>This is a list item of a list with the class 'place-holder'.</li>
</ul>
<pre class='brush: js'>var timer = new window.Timer({ name: 'foo', time: '60'});
var v = new window.TimerView({ model: timer, el: '.place-holder'});
v.render();
timer.set({name: 'bar', time: '2', unit: 'h'});</pre>
</div>
<div class='slide'>
<h2>7. Routers</h2>
<p>Routers handle URL history and matching.</p>
<pre class='brush: coffee'>class window.Routes extends Backbone.Router

 routes:
 '': 'home',
 'search/:query': 'search',
 'search/:query/p:page': 'search'

 home: ->
 @view = new window.TimerView
 @view.render()

 search: (query, page) ->
 'foo'</pre>
<pre class='brush: coffee'>try
 0 / undefined
catch error
 window.router.navigate('error-notification', true)</pre>
</div>
<div class='slide'>
<h2>8. Collections</h2>
<p>A collection contains a set of model instances and exposes events to react upon adding or removing items. Underscore provides convenience methods such as forEach(each), find(), add(), get(), remove() and filter(select).</p>
<pre class='brush: coffee'>class window.Timers extends Backbone.Collection

 model: window.Timer</pre>
An example to bind collection events to a view:
<pre class='brush: js'>var m = new window.Timer({ name: 'foo', time: '60'});
var new_timer_view = new window.NewTimer({ model: m});
new_timer_view.render();
window.timers = new window.Timers();
var cv = new window.TimersView({ collection: window.timers });
cv.render();</pre>
<div class='new-timer'></div>
<ul class='timers'></ul>
</div>
<div class='slide'>
<h2>9. Backend & Collection persistency</h2>
<p>Backbone offers a RESTful JSON interface connecting to an arbitrary backend (using jQuery). Allows to override the default implementation (Backbone.sync), e.g. in order to use local storage.</p>
Models:
<ul>
<li>fetch(): Resets the model's state from the server.</li>
<li>save(): Saves the model if it's valid.</li>
<li>destroy()</li>
<li>isNew(), hasChanged()</li>
</ul>
Collections:
<ul>
<li>fetch(): Replaces the collection with a default set.</li>
<li>create(attr): Creates and adds a model to the collection.</li>
</ul>
</div>
<div class='slide'>
<h2>Part II</h2>
<ol>
<li>BDD with Jasmine</li>
<li>Sinon spies and stubs</li>
<li>Model specs</li>
<li>View specs</li>
<li>Router specs</li>
<li>Collection specs</li>
</ol>
</div>
<div class='slide'>
<h2>1. BDD with Jasmine</h2>
<p>Offers rspec like sytnax:</p>
<pre class='brush: coffee'>describe 'when instantiated without a paramter', ->

 beforeEach ->
 @timer = new window.timer

 it 'should not have a default name', ->
 expect(@timer.get('name')).toBeUndefined
</pre>
<p>It's fast, due to the shallow framework stack.</p>
</div>
<div class='slide'>
<h2>2. Sinon spies and stubs</h2>
<p>A spy records what's going on with a function after it's been called.</p>
<pre class='brush: coffee'>it 'should call a certain function', ->
 @spy = sinon.spy()
 @class.bind 'certain_function', @spy
 @class.someOtherFunctionCall()
 expect(@spy).toHaveBeenCalledOnce()
 expect(@spy).toHaveBeenCalledWith 'bar'</pre>
<p>Stubs replace existing functions with pre-programmed behavior in order to avoid calling functionality that's tested elsewhere.</p>
<pre class='brush: coffee'>beforeEach ->
 @view = window.TimerView
 @viewStub =
 sinon.stub(window, 'TimerView').returns(new Backbone.View)
 @view.render()

afterEach ->
 @viewStub.restore()

it 'should do something not related to render()', ->
 expect($(@view.el).html()).toContain('some content')</pre>
</div>
<div class='slide'>
<h2>3. Model specs</h2>
<p>This example demonstrates the use of sinon spies to watch for validation errors.</p>
<pre class='brush: coffee'>describe 'Timer model', ->

 describe 'when instantiated with a paramter', ->

 beforeEach ->
 @timer = new window.Timer
 name: 'foo'
 time: 10

 it 'should exhibit attributes', ->
 expect(@timer.get('name')).toEqual 'foo'

 it 'should set the unit to seconds by default', ->
 expect(@timer.get('unit')).toEqual 's'

 describe 'validation', ->

 it 'should not save with invalid end time', ->
 @eventSpy = sinon.spy()
 @timer.bind 'error', @eventSpy
 date = new Date()
 date.setDate(date.getDate() - 1)
 @timer.save
 end: date
 expect(@eventSpy).toHaveBeenCalledOnce()
 expect(@eventSpy).toHaveBeenCalledWith
 @timer
 "Timer can't be in the past"

 describe 'when instantiated without a paramter', ->

 beforeEach ->
 @timer = new window.Timer

 it 'should not have a default name', ->
 expect(@timer.get('name')).toBeUndefined()</pre>
</div>
<div class='slide'>
<h2>4. View specs</h2>
<p>Example</p>
<pre class='brush: coffee'>describe 'TimerView', ->

 beforeEach ->
 timer = new window.Timer
 name: 'foo'
 time: '60'
 this.view = new window.TimerView
 model: timer

 afterEach ->

 describe 'Instantiation', ->

 it 'should create li element', ->
 expect($(@view.el)).toBe 'li'

 describe 'Rendering', ->

 beforeEach ->
 @view.render()

 it 'should add the element to the DOM', ->
 expect($(@view.el).html()).toContain 'foo'</pre>
</div>
<div class='slide'>
<h2>5. Router specs</h2>
<p>This example clearly demonstrates the advantage of using stubs.</p>
<pre class='brush: coffee'>describe 'Routes', ->

 beforeEach ->
 @router = new window.Routes
 @routeSpy = sinon.spy()
 try
 Backbone.history.start
 silent: true,
 pushState:true
 catch e
 @router.navigate 'elsewhere'

 it 'fires the home route with a blank hash', ->
 @timerView = new Backbone.View
 @timerView.render = sinon.stub()
 @timerViewStub = sinon.stub(window, 'TimerView').returns(@timerView)

 @router.bind 'route:home', @routeSpy
 @router.navigate '', true
 expect(@routeSpy).toHaveBeenCalledOnce()
 expect(@routeSpy).toHaveBeenCalledWith()

 @timerViewStub.restore()


describe 'Routing', ->

 beforeEach ->
 @router = new window.Routes

 describe 'home handler', ->

 beforeEach ->
 @custom_view = new Backbone.View()
 @custom_view.render = sinon.stub()
 @view =
 sinon.stub(window, 'TimerView').returns(@custom_view)
 @router.home()

 afterEach ->
 @view.restore()

 it 'creates a TimerView view', ->
 expect(@view).toHaveBeenCalledOnce()
 expect(@view).toHaveBeenCalledWith()

 it 'calls the render method of TimerView', ->
 expect(@custom_view.render).toHaveBeenCalledOnce()
 expect(@custom_view.render).toHaveBeenCalledWith()</pre>
</div>
<div class='slide'>
<h2>6. Collection specs</h2>
<p>Example</p>
<pre class='brush: coffee'>describe 'Timers collection', ->

 beforeEach ->

 @timer1 = new Backbone.Model
 id: 1
 name: 'foo'
 @timer2 = new Backbone.Model
 id: 2
 name: 'bar'

 @timers = new window.Timers
 @timerStub = sinon.stub window, 'Timer'

 afterEach ->
 @timerStub.restore()

 describe 'When instantiated with model literal', ->

 beforeEach ->
 @model = new Backbone.Model
 id: 3
 name: 'foobar'
 @timerStub.returns @model
 @timers.model = window.Timer
 @timers.add
 id: 3
 name: 'foobar'

 it 'should have one Timer model', ->
 expect(@timers.length).toEqual 1
 expect(@timerStub).toHaveBeenCalled()

 it 'should find a model by id', ->
 expect(@timers.get(3).get('id')).toEqual @model.get('id')

 it 'should find a model by index', ->
 expect(@timers.at(0).get('id')).toEqual @model.get('id')

 it 'should have called the Timer constructor', ->
 expect(@timerStub).toHaveBeenCalledOnce()
 expect(@timerStub).toHaveBeenCalledWith
 id:3
 name: 'foobar'

 describe 'When adding models', ->

 it 'should order models by id by default', ->
 @timers.model = window.Timer
 @timers.add([@timer1, @timer2])
 expect(@timers.at(0)).toBe @timer1
 expect(@timers.at(1)).toBe @timer2</pre>
</div>
<div class='slide'>
<h2>That's it!</h2>
</div>
</div>
</div>
<div id='footer'>
<div class='container'>
<div class='legal'>
© 2011 Falko Schmidt, Panter llc
</div>
</div>
</div>
</body>
</html>