5
<title>todos.js</title>
6
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
7
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
8
<link rel="stylesheet" media="all" href="docco.css" />
12
<div id="background"></div>
16
<a class="large" href="javascript:void(0);">Jump To …</a>
17
<a class="small" href="javascript:void(0);">+</a>
18
<div id="jump_wrapper">
22
<a class="source" href="backbone.localstorage.html">
23
backbone.localstorage.js
27
<a class="source" href="todos.html">
38
<div class="annotation">
46
<div class="annotation">
48
<div class="pilwrap ">
49
<a class="pilcrow" href="#section-1">¶</a>
51
<p>An example Backbone application contributed by
52
<a href="http://jgn.me/">JĂ©rĂ´me Gravel-Niquet</a>. This demo uses a simple
53
<a href="backbone-localstorage.html">LocalStorage adapter</a>
54
to persist Backbone models within your browser.
57
<p>Load the application once the DOM is ready, using <code>jQuery.ready</code>:
62
<div class="content"><div class='highlight'><pre>$(<span class="keyword">function</span>(){</pre></div></div>
68
<div class="annotation">
70
<div class="pilwrap for-h2">
71
<a class="pilcrow" href="#section-2">¶</a>
81
<div class="annotation">
83
<div class="pilwrap ">
84
<a class="pilcrow" href="#section-3">¶</a>
86
<p>Our basic <strong>Todo</strong> model has <code>title</code>, <code>order</code>, and <code>done</code> attributes.
91
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> Todo = Backbone.Model.extend({</pre></div></div>
97
<div class="annotation">
99
<div class="pilwrap ">
100
<a class="pilcrow" href="#section-4">¶</a>
102
<p>Default attributes for the todo item.
107
<div class="content"><div class='highlight'><pre> defaults: <span class="keyword">function</span>() {
108
<span class="keyword">return</span> {
109
title: <span class="string">"empty todo..."</span>,
110
order: Todos.nextOrder(),
111
done: <span class="literal">false</span>
119
<div class="annotation">
121
<div class="pilwrap ">
122
<a class="pilcrow" href="#section-5">¶</a>
124
<p>Toggle the <code>done</code> state of this todo item.
129
<div class="content"><div class='highlight'><pre> toggle: <span class="keyword">function</span>() {
130
<span class="keyword">this</span>.save({done: !<span class="keyword">this</span>.get(<span class="string">"done"</span>)});
133
});</pre></div></div>
139
<div class="annotation">
141
<div class="pilwrap for-h2">
142
<a class="pilcrow" href="#section-6">¶</a>
144
<h2>Todo Collection</h2>
152
<div class="annotation">
154
<div class="pilwrap ">
155
<a class="pilcrow" href="#section-7">¶</a>
157
<p>The collection of todos is backed by <em>localStorage</em> instead of a remote
163
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> TodoList = Backbone.Collection.extend({</pre></div></div>
169
<div class="annotation">
171
<div class="pilwrap ">
172
<a class="pilcrow" href="#section-8">¶</a>
174
<p>Reference to this collection's model.
179
<div class="content"><div class='highlight'><pre> model: Todo,</pre></div></div>
185
<div class="annotation">
187
<div class="pilwrap ">
188
<a class="pilcrow" href="#section-9">¶</a>
190
<p>Save all of the todo items under the <code>"todos-backbone"</code> namespace.
195
<div class="content"><div class='highlight'><pre> localStorage: <span class="keyword">new</span> Backbone.LocalStorage(<span class="string">"todos-backbone"</span>),</pre></div></div>
201
<div class="annotation">
203
<div class="pilwrap ">
204
<a class="pilcrow" href="#section-10">¶</a>
206
<p>Filter down the list of all todo items that are finished.
211
<div class="content"><div class='highlight'><pre> done: <span class="keyword">function</span>() {
212
<span class="keyword">return</span> <span class="keyword">this</span>.where({done: <span class="literal">true</span>});
219
<div class="annotation">
221
<div class="pilwrap ">
222
<a class="pilcrow" href="#section-11">¶</a>
224
<p>Filter down the list to only todo items that are still not finished.
229
<div class="content"><div class='highlight'><pre> remaining: <span class="keyword">function</span>() {
230
<span class="keyword">return</span> <span class="keyword">this</span>.without.apply(<span class="keyword">this</span>, <span class="keyword">this</span>.done());
237
<div class="annotation">
239
<div class="pilwrap ">
240
<a class="pilcrow" href="#section-12">¶</a>
242
<p>We keep the Todos in sequential order, despite being saved by unordered
243
GUID in the database. This generates the next order number for new items.
248
<div class="content"><div class='highlight'><pre> nextOrder: <span class="keyword">function</span>() {
249
<span class="keyword">if</span> (!<span class="keyword">this</span>.length) <span class="keyword">return</span> <span class="number">1</span>;
250
<span class="keyword">return</span> <span class="keyword">this</span>.last().get(<span class="string">'order'</span>) + <span class="number">1</span>;
257
<div class="annotation">
259
<div class="pilwrap ">
260
<a class="pilcrow" href="#section-13">¶</a>
262
<p>Todos are sorted by their original insertion order.
267
<div class="content"><div class='highlight'><pre> comparator: <span class="string">'order'</span>
269
});</pre></div></div>
275
<div class="annotation">
277
<div class="pilwrap ">
278
<a class="pilcrow" href="#section-14">¶</a>
280
<p>Create our global collection of <strong>Todos</strong>.
285
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> Todos = <span class="keyword">new</span> TodoList;</pre></div></div>
291
<div class="annotation">
293
<div class="pilwrap for-h2">
294
<a class="pilcrow" href="#section-15">¶</a>
296
<h2>Todo Item View</h2>
304
<div class="annotation">
306
<div class="pilwrap ">
307
<a class="pilcrow" href="#section-16">¶</a>
309
<p>The DOM element for a todo item...
314
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> TodoView = Backbone.View.extend({</pre></div></div>
320
<div class="annotation">
322
<div class="pilwrap ">
323
<a class="pilcrow" href="#section-17">¶</a>
325
<p>... is a list tag.
330
<div class="content"><div class='highlight'><pre> tagName: <span class="string">"li"</span>,</pre></div></div>
336
<div class="annotation">
338
<div class="pilwrap ">
339
<a class="pilcrow" href="#section-18">¶</a>
341
<p>Cache the template function for a single item.
346
<div class="content"><div class='highlight'><pre> template: _.template($(<span class="string">'#item-template'</span>).html()),</pre></div></div>
352
<div class="annotation">
354
<div class="pilwrap ">
355
<a class="pilcrow" href="#section-19">¶</a>
357
<p>The DOM events specific to an item.
362
<div class="content"><div class='highlight'><pre> events: {
363
<span class="string">"click .toggle"</span> : <span class="string">"toggleDone"</span>,
364
<span class="string">"dblclick .view"</span> : <span class="string">"edit"</span>,
365
<span class="string">"click a.destroy"</span> : <span class="string">"clear"</span>,
366
<span class="string">"keypress .edit"</span> : <span class="string">"updateOnEnter"</span>,
367
<span class="string">"blur .edit"</span> : <span class="string">"close"</span>
374
<div class="annotation">
376
<div class="pilwrap ">
377
<a class="pilcrow" href="#section-20">¶</a>
379
<p>The TodoView listens for changes to its model, re-rendering. Since there's
380
a one-to-one correspondence between a <strong>Todo</strong> and a <strong>TodoView</strong> in this
381
app, we set a direct reference on the model for convenience.
386
<div class="content"><div class='highlight'><pre> initialize: <span class="keyword">function</span>() {
387
<span class="keyword">this</span>.listenTo(<span class="keyword">this</span>.model, <span class="string">'change'</span>, <span class="keyword">this</span>.render);
388
<span class="keyword">this</span>.listenTo(<span class="keyword">this</span>.model, <span class="string">'destroy'</span>, <span class="keyword">this</span>.remove);
395
<div class="annotation">
397
<div class="pilwrap ">
398
<a class="pilcrow" href="#section-21">¶</a>
400
<p>Re-render the titles of the todo item.
405
<div class="content"><div class='highlight'><pre> render: <span class="keyword">function</span>() {
406
<span class="keyword">this</span>.$el.html(<span class="keyword">this</span>.template(<span class="keyword">this</span>.model.toJSON()));
407
<span class="keyword">this</span>.$el.toggleClass(<span class="string">'done'</span>, <span class="keyword">this</span>.model.get(<span class="string">'done'</span>));
408
<span class="keyword">this</span>.input = <span class="keyword">this</span>.$(<span class="string">'.edit'</span>);
409
<span class="keyword">return</span> <span class="keyword">this</span>;
416
<div class="annotation">
418
<div class="pilwrap ">
419
<a class="pilcrow" href="#section-22">¶</a>
421
<p>Toggle the <code>"done"</code> state of the model.
426
<div class="content"><div class='highlight'><pre> toggleDone: <span class="keyword">function</span>() {
427
<span class="keyword">this</span>.model.toggle();
434
<div class="annotation">
436
<div class="pilwrap ">
437
<a class="pilcrow" href="#section-23">¶</a>
439
<p>Switch this view into <code>"editing"</code> mode, displaying the input field.
444
<div class="content"><div class='highlight'><pre> edit: <span class="keyword">function</span>() {
445
<span class="keyword">this</span>.$el.addClass(<span class="string">"editing"</span>);
446
<span class="keyword">this</span>.input.focus();
453
<div class="annotation">
455
<div class="pilwrap ">
456
<a class="pilcrow" href="#section-24">¶</a>
458
<p>Close the <code>"editing"</code> mode, saving changes to the todo.
463
<div class="content"><div class='highlight'><pre> close: <span class="keyword">function</span>() {
464
<span class="keyword">var</span> value = <span class="keyword">this</span>.input.val();
465
<span class="keyword">if</span> (!value) {
466
<span class="keyword">this</span>.clear();
467
} <span class="keyword">else</span> {
468
<span class="keyword">this</span>.model.save({title: value});
469
<span class="keyword">this</span>.$el.removeClass(<span class="string">"editing"</span>);
477
<div class="annotation">
479
<div class="pilwrap ">
480
<a class="pilcrow" href="#section-25">¶</a>
482
<p>If you hit <code>enter</code>, we're through editing the item.
487
<div class="content"><div class='highlight'><pre> updateOnEnter: <span class="keyword">function</span>(e) {
488
<span class="keyword">if</span> (e.keyCode == <span class="number">13</span>) <span class="keyword">this</span>.close();
495
<div class="annotation">
497
<div class="pilwrap ">
498
<a class="pilcrow" href="#section-26">¶</a>
500
<p>Remove the item, destroy the model.
505
<div class="content"><div class='highlight'><pre> clear: <span class="keyword">function</span>() {
506
<span class="keyword">this</span>.model.destroy();
509
});</pre></div></div>
515
<div class="annotation">
517
<div class="pilwrap for-h2">
518
<a class="pilcrow" href="#section-27">¶</a>
520
<h2>The Application</h2>
528
<div class="annotation">
530
<div class="pilwrap ">
531
<a class="pilcrow" href="#section-28">¶</a>
533
<p>Our overall <strong>AppView</strong> is the top-level piece of UI.
538
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> AppView = Backbone.View.extend({</pre></div></div>
544
<div class="annotation">
546
<div class="pilwrap ">
547
<a class="pilcrow" href="#section-29">¶</a>
549
<p>Instead of generating a new element, bind to the existing skeleton of
550
the App already present in the HTML.
555
<div class="content"><div class='highlight'><pre> el: $(<span class="string">"#todoapp"</span>),</pre></div></div>
561
<div class="annotation">
563
<div class="pilwrap ">
564
<a class="pilcrow" href="#section-30">¶</a>
566
<p>Our template for the line of statistics at the bottom of the app.
571
<div class="content"><div class='highlight'><pre> statsTemplate: _.template($(<span class="string">'#stats-template'</span>).html()),</pre></div></div>
577
<div class="annotation">
579
<div class="pilwrap ">
580
<a class="pilcrow" href="#section-31">¶</a>
582
<p>Delegated events for creating new items, and clearing completed ones.
587
<div class="content"><div class='highlight'><pre> events: {
588
<span class="string">"keypress #new-todo"</span>: <span class="string">"createOnEnter"</span>,
589
<span class="string">"click #clear-completed"</span>: <span class="string">"clearCompleted"</span>,
590
<span class="string">"click #toggle-all"</span>: <span class="string">"toggleAllComplete"</span>
597
<div class="annotation">
599
<div class="pilwrap ">
600
<a class="pilcrow" href="#section-32">¶</a>
602
<p>At initialization we bind to the relevant events on the <code>Todos</code>
603
collection, when items are added or changed. Kick things off by
604
loading any preexisting todos that might be saved in <em>localStorage</em>.
609
<div class="content"><div class='highlight'><pre> initialize: <span class="keyword">function</span>() {
611
<span class="keyword">this</span>.input = <span class="keyword">this</span>.$(<span class="string">"#new-todo"</span>);
612
<span class="keyword">this</span>.allCheckbox = <span class="keyword">this</span>.$(<span class="string">"#toggle-all"</span>)[<span class="number">0</span>];
614
<span class="keyword">this</span>.listenTo(Todos, <span class="string">'add'</span>, <span class="keyword">this</span>.addOne);
615
<span class="keyword">this</span>.listenTo(Todos, <span class="string">'reset'</span>, <span class="keyword">this</span>.addAll);
616
<span class="keyword">this</span>.listenTo(Todos, <span class="string">'all'</span>, <span class="keyword">this</span>.render);
618
<span class="keyword">this</span>.footer = <span class="keyword">this</span>.$(<span class="string">'footer'</span>);
619
<span class="keyword">this</span>.main = $(<span class="string">'#main'</span>);
628
<div class="annotation">
630
<div class="pilwrap ">
631
<a class="pilcrow" href="#section-33">¶</a>
633
<p>Re-rendering the App just means refreshing the statistics -- the rest
634
of the app doesn't change.
639
<div class="content"><div class='highlight'><pre> render: <span class="keyword">function</span>() {
640
<span class="keyword">var</span> done = Todos.done().length;
641
<span class="keyword">var</span> remaining = Todos.remaining().length;
643
<span class="keyword">if</span> (Todos.length) {
644
<span class="keyword">this</span>.main.show();
645
<span class="keyword">this</span>.footer.show();
646
<span class="keyword">this</span>.footer.html(<span class="keyword">this</span>.statsTemplate({done: done, remaining: remaining}));
647
} <span class="keyword">else</span> {
648
<span class="keyword">this</span>.main.hide();
649
<span class="keyword">this</span>.footer.hide();
652
<span class="keyword">this</span>.allCheckbox.checked = !remaining;
659
<div class="annotation">
661
<div class="pilwrap ">
662
<a class="pilcrow" href="#section-34">¶</a>
664
<p>Add a single todo item to the list by creating a view for it, and
665
appending its element to the <code><ul></code>.
670
<div class="content"><div class='highlight'><pre> addOne: <span class="keyword">function</span>(todo) {
671
<span class="keyword">var</span> view = <span class="keyword">new</span> TodoView({model: todo});
672
<span class="keyword">this</span>.$(<span class="string">"#todo-list"</span>).append(view.render().el);
679
<div class="annotation">
681
<div class="pilwrap ">
682
<a class="pilcrow" href="#section-35">¶</a>
684
<p>Add all items in the <strong>Todos</strong> collection at once.
689
<div class="content"><div class='highlight'><pre> addAll: <span class="keyword">function</span>() {
690
Todos.each(<span class="keyword">this</span>.addOne, <span class="keyword">this</span>);
697
<div class="annotation">
699
<div class="pilwrap ">
700
<a class="pilcrow" href="#section-36">¶</a>
702
<p>If you hit return in the main input field, create new <strong>Todo</strong> model,
703
persisting it to <em>localStorage</em>.
708
<div class="content"><div class='highlight'><pre> createOnEnter: <span class="keyword">function</span>(e) {
709
<span class="keyword">if</span> (e.keyCode != <span class="number">13</span>) <span class="keyword">return</span>;
710
<span class="keyword">if</span> (!<span class="keyword">this</span>.input.val()) <span class="keyword">return</span>;
712
Todos.create({title: <span class="keyword">this</span>.input.val()});
713
<span class="keyword">this</span>.input.val(<span class="string">''</span>);
720
<div class="annotation">
722
<div class="pilwrap ">
723
<a class="pilcrow" href="#section-37">¶</a>
725
<p>Clear all done todo items, destroying their models.
730
<div class="content"><div class='highlight'><pre> clearCompleted: <span class="keyword">function</span>() {
731
_.invoke(Todos.done(), <span class="string">'destroy'</span>);
732
<span class="keyword">return</span> <span class="literal">false</span>;
735
toggleAllComplete: <span class="function"><span class="keyword">function</span> <span class="params">()</span> {</span>
736
<span class="keyword">var</span> done = <span class="keyword">this</span>.allCheckbox.checked;
737
Todos.each(<span class="function"><span class="keyword">function</span> <span class="params">(todo)</span> {</span> todo.save({<span class="string">'done'</span>: done}); });
740
});</pre></div></div>
746
<div class="annotation">
748
<div class="pilwrap ">
749
<a class="pilcrow" href="#section-38">¶</a>
751
<p>Finally, we kick things off by creating the <strong>App</strong>.
756
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> App = <span class="keyword">new</span> AppView;
758
});</pre></div></div>