<!DOCTYPE html> <html> <head> <title>todos.js</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"> <link rel="stylesheet" media="all" href="docco.css" /> </head> <body> <div id="container"> <div id="background"></div> <ul id="jump_to"> <li> <a class="large" href="javascript:void(0);">Jump To …</a> <a class="small" href="javascript:void(0);">+</a> <div id="jump_wrapper"> <div id="jump_page"> <a class="source" href="backbone.localstorage.html"> backbone.localstorage.js </a> <a class="source" href="todos.html"> todos.js </a> </div> </li> </ul> <ul class="sections"> <li id="title"> <div class="annotation"> <h1>todos.js</h1> </div> </li> <li id="section-1"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-1">¶</a> </div> <p>An example Backbone application contributed by <a href="http://jgn.me/">Jérôme Gravel-Niquet</a>. This demo uses a simple <a href="backbone-localstorage.html">LocalStorage adapter</a> to persist Backbone models within your browser.</p> <p>Load the application once the DOM is ready, using <code>jQuery.ready</code>:</p> </div> <div class="content"><div class='highlight'><pre>$(<span class="keyword">function</span>(){</pre></div></div> </li> <li id="section-2"> <div class="annotation"> <div class="pilwrap for-h2"> <a class="pilcrow" href="#section-2">¶</a> </div> <h2>Todo Model</h2> </div> </li> <li id="section-3"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-3">¶</a> </div> <p>Our basic <strong>Todo</strong> model has <code>title</code>, <code>order</code>, and <code>done</code> attributes.</p> </div> <div class="content"><div class='highlight'><pre> <span class="keyword">var</span> Todo = Backbone.Model.extend({</pre></div></div> </li> <li id="section-4"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-4">¶</a> </div> <p>Default attributes for the todo item.</p> </div> <div class="content"><div class='highlight'><pre> defaults: <span class="keyword">function</span>() { <span class="keyword">return</span> { title: <span class="string">"empty todo..."</span>, order: Todos.nextOrder(), done: <span class="literal">false</span> }; },</pre></div></div> </li> <li id="section-5"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-5">¶</a> </div> <p>Toggle the <code>done</code> state of this todo item.</p> </div> <div class="content"><div class='highlight'><pre> toggle: <span class="keyword">function</span>() { <span class="keyword">this</span>.save({done: !<span class="keyword">this</span>.get(<span class="string">"done"</span>)}); } });</pre></div></div> </li> <li id="section-6"> <div class="annotation"> <div class="pilwrap for-h2"> <a class="pilcrow" href="#section-6">¶</a> </div> <h2>Todo Collection</h2> </div> </li> <li id="section-7"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-7">¶</a> </div> <p>The collection of todos is backed by <em>localStorage</em> instead of a remote server.</p> </div> <div class="content"><div class='highlight'><pre> <span class="keyword">var</span> TodoList = Backbone.Collection.extend({</pre></div></div> </li> <li id="section-8"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-8">¶</a> </div> <p>Reference to this collection's model.</p> </div> <div class="content"><div class='highlight'><pre> model: Todo,</pre></div></div> </li> <li id="section-9"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-9">¶</a> </div> <p>Save all of the todo items under the <code>"todos-backbone"</code> namespace.</p> </div> <div class="content"><div class='highlight'><pre> localStorage: <span class="keyword">new</span> Backbone.LocalStorage(<span class="string">"todos-backbone"</span>),</pre></div></div> </li> <li id="section-10"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-10">¶</a> </div> <p>Filter down the list of all todo items that are finished.</p> </div> <div class="content"><div class='highlight'><pre> done: <span class="keyword">function</span>() { <span class="keyword">return</span> <span class="keyword">this</span>.where({done: <span class="literal">true</span>}); },</pre></div></div> </li> <li id="section-11"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-11">¶</a> </div> <p>Filter down the list to only todo items that are still not finished.</p> </div> <div class="content"><div class='highlight'><pre> remaining: <span class="keyword">function</span>() { <span class="keyword">return</span> <span class="keyword">this</span>.where({done: <span class="literal">false</span>}); },</pre></div></div> </li> <li id="section-12"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-12">¶</a> </div> <p>We keep the Todos in sequential order, despite being saved by unordered GUID in the database. This generates the next order number for new items.</p> </div> <div class="content"><div class='highlight'><pre> nextOrder: <span class="keyword">function</span>() { <span class="keyword">if</span> (!<span class="keyword">this</span>.length) <span class="keyword">return</span> <span class="number">1</span>; <span class="keyword">return</span> <span class="keyword">this</span>.last().get(<span class="string">'order'</span>) + <span class="number">1</span>; },</pre></div></div> </li> <li id="section-13"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-13">¶</a> </div> <p>Todos are sorted by their original insertion order.</p> </div> <div class="content"><div class='highlight'><pre> comparator: <span class="string">'order'</span> });</pre></div></div> </li> <li id="section-14"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-14">¶</a> </div> <p>Create our global collection of <strong>Todos</strong>.</p> </div> <div class="content"><div class='highlight'><pre> <span class="keyword">var</span> Todos = <span class="keyword">new</span> TodoList;</pre></div></div> </li> <li id="section-15"> <div class="annotation"> <div class="pilwrap for-h2"> <a class="pilcrow" href="#section-15">¶</a> </div> <h2>Todo Item View</h2> </div> </li> <li id="section-16"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-16">¶</a> </div> <p>The DOM element for a todo item...</p> </div> <div class="content"><div class='highlight'><pre> <span class="keyword">var</span> TodoView = Backbone.View.extend({</pre></div></div> </li> <li id="section-17"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-17">¶</a> </div> <p>... is a list tag.</p> </div> <div class="content"><div class='highlight'><pre> tagName: <span class="string">"li"</span>,</pre></div></div> </li> <li id="section-18"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-18">¶</a> </div> <p>Cache the template function for a single item.</p> </div> <div class="content"><div class='highlight'><pre> template: _.template($(<span class="string">'#item-template'</span>).html()),</pre></div></div> </li> <li id="section-19"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-19">¶</a> </div> <p>The DOM events specific to an item.</p> </div> <div class="content"><div class='highlight'><pre> events: { <span class="string">"click .toggle"</span> : <span class="string">"toggleDone"</span>, <span class="string">"dblclick .view"</span> : <span class="string">"edit"</span>, <span class="string">"click a.destroy"</span> : <span class="string">"clear"</span>, <span class="string">"keypress .edit"</span> : <span class="string">"updateOnEnter"</span>, <span class="string">"blur .edit"</span> : <span class="string">"close"</span> },</pre></div></div> </li> <li id="section-20"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-20">¶</a> </div> <p>The TodoView listens for changes to its model, re-rendering. Since there's a one-to-one correspondence between a <strong>Todo</strong> and a <strong>TodoView</strong> in this app, we set a direct reference on the model for convenience.</p> </div> <div class="content"><div class='highlight'><pre> initialize: <span class="keyword">function</span>() { <span class="keyword">this</span>.listenTo(<span class="keyword">this</span>.model, <span class="string">'change'</span>, <span class="keyword">this</span>.render); <span class="keyword">this</span>.listenTo(<span class="keyword">this</span>.model, <span class="string">'destroy'</span>, <span class="keyword">this</span>.remove); },</pre></div></div> </li> <li id="section-21"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-21">¶</a> </div> <p>Re-render the titles of the todo item.</p> </div> <div class="content"><div class='highlight'><pre> render: <span class="keyword">function</span>() { <span class="keyword">this</span>.$el.html(<span class="keyword">this</span>.template(<span class="keyword">this</span>.model.toJSON())); <span class="keyword">this</span>.$el.toggleClass(<span class="string">'done'</span>, <span class="keyword">this</span>.model.get(<span class="string">'done'</span>)); <span class="keyword">this</span>.input = <span class="keyword">this</span>.$(<span class="string">'.edit'</span>); <span class="keyword">return</span> <span class="keyword">this</span>; },</pre></div></div> </li> <li id="section-22"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-22">¶</a> </div> <p>Toggle the <code>"done"</code> state of the model.</p> </div> <div class="content"><div class='highlight'><pre> toggleDone: <span class="keyword">function</span>() { <span class="keyword">this</span>.model.toggle(); },</pre></div></div> </li> <li id="section-23"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-23">¶</a> </div> <p>Switch this view into <code>"editing"</code> mode, displaying the input field.</p> </div> <div class="content"><div class='highlight'><pre> edit: <span class="keyword">function</span>() { <span class="keyword">this</span>.$el.addClass(<span class="string">"editing"</span>); <span class="keyword">this</span>.input.focus(); },</pre></div></div> </li> <li id="section-24"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-24">¶</a> </div> <p>Close the <code>"editing"</code> mode, saving changes to the todo.</p> </div> <div class="content"><div class='highlight'><pre> close: <span class="keyword">function</span>() { <span class="keyword">var</span> value = <span class="keyword">this</span>.input.val(); <span class="keyword">if</span> (!value) { <span class="keyword">this</span>.clear(); } <span class="keyword">else</span> { <span class="keyword">this</span>.model.save({title: value}); <span class="keyword">this</span>.$el.removeClass(<span class="string">"editing"</span>); } },</pre></div></div> </li> <li id="section-25"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-25">¶</a> </div> <p>If you hit <code>enter</code>, we're through editing the item.</p> </div> <div class="content"><div class='highlight'><pre> updateOnEnter: <span class="keyword">function</span>(e) { <span class="keyword">if</span> (e.keyCode == <span class="number">13</span>) <span class="keyword">this</span>.close(); },</pre></div></div> </li> <li id="section-26"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-26">¶</a> </div> <p>Remove the item, destroy the model.</p> </div> <div class="content"><div class='highlight'><pre> clear: <span class="keyword">function</span>() { <span class="keyword">this</span>.model.destroy(); } });</pre></div></div> </li> <li id="section-27"> <div class="annotation"> <div class="pilwrap for-h2"> <a class="pilcrow" href="#section-27">¶</a> </div> <h2>The Application</h2> </div> </li> <li id="section-28"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-28">¶</a> </div> <p>Our overall <strong>AppView</strong> is the top-level piece of UI.</p> </div> <div class="content"><div class='highlight'><pre> <span class="keyword">var</span> AppView = Backbone.View.extend({</pre></div></div> </li> <li id="section-29"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-29">¶</a> </div> <p>Instead of generating a new element, bind to the existing skeleton of the App already present in the HTML.</p> </div> <div class="content"><div class='highlight'><pre> el: $(<span class="string">"#todoapp"</span>),</pre></div></div> </li> <li id="section-30"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-30">¶</a> </div> <p>Our template for the line of statistics at the bottom of the app.</p> </div> <div class="content"><div class='highlight'><pre> statsTemplate: _.template($(<span class="string">'#stats-template'</span>).html()),</pre></div></div> </li> <li id="section-31"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-31">¶</a> </div> <p>Delegated events for creating new items, and clearing completed ones.</p> </div> <div class="content"><div class='highlight'><pre> events: { <span class="string">"keypress #new-todo"</span>: <span class="string">"createOnEnter"</span>, <span class="string">"click #clear-completed"</span>: <span class="string">"clearCompleted"</span>, <span class="string">"click #toggle-all"</span>: <span class="string">"toggleAllComplete"</span> },</pre></div></div> </li> <li id="section-32"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-32">¶</a> </div> <p>At initialization we bind to the relevant events on the <code>Todos</code> collection, when items are added or changed. Kick things off by loading any preexisting todos that might be saved in <em>localStorage</em>.</p> </div> <div class="content"><div class='highlight'><pre> initialize: <span class="keyword">function</span>() { <span class="keyword">this</span>.input = <span class="keyword">this</span>.$(<span class="string">"#new-todo"</span>); <span class="keyword">this</span>.allCheckbox = <span class="keyword">this</span>.$(<span class="string">"#toggle-all"</span>)[<span class="number">0</span>]; <span class="keyword">this</span>.listenTo(Todos, <span class="string">'add'</span>, <span class="keyword">this</span>.addOne); <span class="keyword">this</span>.listenTo(Todos, <span class="string">'reset'</span>, <span class="keyword">this</span>.addAll); <span class="keyword">this</span>.listenTo(Todos, <span class="string">'all'</span>, <span class="keyword">this</span>.render); <span class="keyword">this</span>.footer = <span class="keyword">this</span>.$(<span class="string">'footer'</span>); <span class="keyword">this</span>.main = $(<span class="string">'#main'</span>); Todos.fetch(); },</pre></div></div> </li> <li id="section-33"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-33">¶</a> </div> <p>Re-rendering the App just means refreshing the statistics -- the rest of the app doesn't change.</p> </div> <div class="content"><div class='highlight'><pre> render: <span class="keyword">function</span>() { <span class="keyword">var</span> done = Todos.done().length; <span class="keyword">var</span> remaining = Todos.remaining().length; <span class="keyword">if</span> (Todos.length) { <span class="keyword">this</span>.main.show(); <span class="keyword">this</span>.footer.show(); <span class="keyword">this</span>.footer.html(<span class="keyword">this</span>.statsTemplate({done: done, remaining: remaining})); } <span class="keyword">else</span> { <span class="keyword">this</span>.main.hide(); <span class="keyword">this</span>.footer.hide(); } <span class="keyword">this</span>.allCheckbox.checked = !remaining; },</pre></div></div> </li> <li id="section-34"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-34">¶</a> </div> <p>Add a single todo item to the list by creating a view for it, and appending its element to the <code><ul></code>.</p> </div> <div class="content"><div class='highlight'><pre> addOne: <span class="keyword">function</span>(todo) { <span class="keyword">var</span> view = <span class="keyword">new</span> TodoView({model: todo}); <span class="keyword">this</span>.$(<span class="string">"#todo-list"</span>).append(view.render().el); },</pre></div></div> </li> <li id="section-35"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-35">¶</a> </div> <p>Add all items in the <strong>Todos</strong> collection at once.</p> </div> <div class="content"><div class='highlight'><pre> addAll: <span class="keyword">function</span>() { Todos.each(<span class="keyword">this</span>.addOne, <span class="keyword">this</span>); },</pre></div></div> </li> <li id="section-36"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-36">¶</a> </div> <p>If you hit return in the main input field, create new <strong>Todo</strong> model, persisting it to <em>localStorage</em>.</p> </div> <div class="content"><div class='highlight'><pre> createOnEnter: <span class="keyword">function</span>(e) { <span class="keyword">if</span> (e.keyCode != <span class="number">13</span>) <span class="keyword">return</span>; <span class="keyword">if</span> (!<span class="keyword">this</span>.input.val()) <span class="keyword">return</span>; Todos.create({title: <span class="keyword">this</span>.input.val()}); <span class="keyword">this</span>.input.val(<span class="string">''</span>); },</pre></div></div> </li> <li id="section-37"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-37">¶</a> </div> <p>Clear all done todo items, destroying their models.</p> </div> <div class="content"><div class='highlight'><pre> clearCompleted: <span class="keyword">function</span>() { _.invoke(Todos.done(), <span class="string">'destroy'</span>); <span class="keyword">return</span> <span class="literal">false</span>; }, toggleAllComplete: <span class="function"><span class="keyword">function</span> <span class="params">()</span> {</span> <span class="keyword">var</span> done = <span class="keyword">this</span>.allCheckbox.checked; Todos.each(<span class="function"><span class="keyword">function</span> <span class="params">(todo)</span> {</span> todo.save({<span class="string">'done'</span>: done}); }); } });</pre></div></div> </li> <li id="section-38"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-38">¶</a> </div> <p>Finally, we kick things off by creating the <strong>App</strong>.</p> </div> <div class="content"><div class='highlight'><pre> <span class="keyword">var</span> App = <span class="keyword">new</span> AppView; });</pre></div></div> </li> </ul> </div> </body> </html>