Created
November 23, 2013 02:39
-
-
Save imalberto/7610099 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <script src="http://documentcloud.github.io/underscore/underscore-min.js"></script> | |
| <script src="http://documentcloud.github.io/backbone/backbone-min.js"></script> | |
| <meta name="description" content="Test driving jQuery" /> | |
| <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/themes/smoothness/jquery-ui.min.css" rel="stylesheet" type="text/css" /> | |
| <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> | |
| <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script> | |
| <meta charset=utf-8 /> | |
| <title>JS Bin</title> | |
| </head> | |
| <body> | |
| <script type="text/template" id="item-template"> | |
| <div class="view"> | |
| <input class="toggle" type="checkbox" <%= completed ? 'checked' : '' %>> | |
| <label><%- title %></label> | |
| <button class="destroy"></button> | |
| </div> | |
| <input class="edit" value="<%- title %>"> | |
| </script> | |
| <script type="text/template" id="stats-template"> | |
| <span id="todo-count"><strong><%= remaining %></strong> <%= remaining === 1 ? 'item' : 'items' %> left</span> | |
| <ul id="filters"> | |
| <li> | |
| <a class="selected" href="#/">All</a> | |
| </li> | |
| <li> | |
| <a href="#/active">Active</a> | |
| </li> | |
| <li> | |
| <a href="#/completed">Completed</a> | |
| </li> | |
| </ul> | |
| <% if (completed) { %> | |
| <button id="clear-completed">Clear completed (<%= completed %>)</button> | |
| <% } %> | |
| </script> | |
| <section id="todoapp"> | |
| <header id="header"> | |
| <h1>todos</h1> | |
| <input id="new-todo" placeholder="What needs to be done?" autofocus> | |
| </header> | |
| <section id="main"> | |
| <input id="toggle-all" type="checkbox"> | |
| <label for="toggle-all">Mark all as complete</label> | |
| <ul id="todo-list"></ul> | |
| </section> | |
| <footer id="footer"></footer> | |
| </section> | |
| <footer id="info"> | |
| <p>Double-click to edit a todo</p> | |
| <p>Written by <a href="https://github.com/addyosmani">Addy Osmani</a></p> | |
| <p>Part of <a href="http://todomvc.com">TodoMVC</a></p> | |
| </footer> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function (root, factory) { | |
| if (typeof define === "function" && define.amd) { | |
| // AMD. Register as an anonymous module. | |
| define(["underscore","backbone"], function(_, Backbone) { | |
| // Use global variables if the locals is undefined. | |
| return factory(_ || root._, Backbone || root.Backbone); | |
| }); | |
| } else { | |
| // RequireJS isn't being used. Assume underscore and backbone is loaded in <script> tags | |
| factory(_, Backbone); | |
| } | |
| }(this, function(_, Backbone) { | |
| // A simple module to replace `Backbone.sync` with *localStorage*-based | |
| // persistence. Models are given GUIDS, and saved into a JSON object. Simple | |
| // as that. | |
| // Hold reference to Underscore.js and Backbone.js in the closure in order | |
| // to make things work even if they are removed from the global namespace | |
| // Generate four random hex digits. | |
| function S4() { | |
| return (((1+Math.random())*0x10000)|0).toString(16).substring(1); | |
| } | |
| // Generate a pseudo-GUID by concatenating random hexadecimal. | |
| function guid() { | |
| return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); | |
| } | |
| // Our Store is represented by a single JS object in *localStorage*. Create it | |
| // with a meaningful name, like the name you'd give a table. | |
| // window.Store is deprectated, use Backbone.LocalStorage instead | |
| Backbone.LocalStorage = window.Store = function(name) { | |
| this.name = name; | |
| var store = this.localStorage().getItem(this.name); | |
| this.records = (store && store.split(",")) || []; | |
| }; | |
| _.extend(Backbone.LocalStorage.prototype, { | |
| // Save the current state of the **Store** to *localStorage*. | |
| save: function() { | |
| this.localStorage().setItem(this.name, this.records.join(",")); | |
| }, | |
| // Add a model, giving it a (hopefully)-unique GUID, if it doesn't already | |
| // have an id of it's own. | |
| create: function(model) { | |
| if (!model.id) { | |
| model.id = guid(); | |
| model.set(model.idAttribute, model.id); | |
| } | |
| this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model)); | |
| this.records.push(model.id.toString()); | |
| this.save(); | |
| return this.find(model); | |
| }, | |
| // Update a model by replacing its copy in `this.data`. | |
| update: function(model) { | |
| this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model)); | |
| if (!_.include(this.records, model.id.toString())) | |
| this.records.push(model.id.toString()); this.save(); | |
| return this.find(model); | |
| }, | |
| // Retrieve a model from `this.data` by id. | |
| find: function(model) { | |
| return this.jsonData(this.localStorage().getItem(this.name+"-"+model.id)); | |
| }, | |
| // Return the array of all models currently in storage. | |
| findAll: function() { | |
| return _(this.records).chain() | |
| .map(function(id){ | |
| return this.jsonData(this.localStorage().getItem(this.name+"-"+id)); | |
| }, this) | |
| .compact() | |
| .value(); | |
| }, | |
| // Delete a model from `this.data`, returning it. | |
| destroy: function(model) { | |
| if (model.isNew()) | |
| return false; | |
| this.localStorage().removeItem(this.name+"-"+model.id); | |
| this.records = _.reject(this.records, function(id){ | |
| return id === model.id.toString(); | |
| }); | |
| this.save(); | |
| return model; | |
| }, | |
| localStorage: function() { | |
| return localStorage; | |
| }, | |
| // fix for "illegal access" error on Android when JSON.parse is passed null | |
| jsonData: function (data) { | |
| return data && JSON.parse(data); | |
| } | |
| }); | |
| // localSync delegate to the model or collection's | |
| // *localStorage* property, which should be an instance of `Store`. | |
| // window.Store.sync and Backbone.localSync is deprectated, use Backbone.LocalStorage.sync instead | |
| Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options) { | |
| var store = model.localStorage || model.collection.localStorage; | |
| var resp, errorMessage, syncDfd = $.Deferred && $.Deferred(); //If $ is having Deferred - use it. | |
| try { | |
| switch (method) { | |
| case "read": | |
| resp = model.id !== undefined ? store.find(model) : store.findAll(); | |
| break; | |
| case "create": | |
| resp = store.create(model); | |
| break; | |
| case "update": | |
| resp = store.update(model); | |
| break; | |
| case "delete": | |
| resp = store.destroy(model); | |
| break; | |
| } | |
| } catch(error) { | |
| if (error.code === DOMException.QUOTA_EXCEEDED_ERR && window.localStorage.length === 0) | |
| errorMessage = "Private browsing is unsupported"; | |
| else | |
| errorMessage = error.message; | |
| } | |
| if (resp) { | |
| if (options && options.success) | |
| if (Backbone.VERSION === "0.9.10") { | |
| options.success(model, resp, options); | |
| } else { | |
| options.success(resp); | |
| } | |
| if (syncDfd) | |
| syncDfd.resolve(resp); | |
| } else { | |
| errorMessage = errorMessage ? errorMessage | |
| : "Record Not Found"; | |
| if (options && options.error) | |
| if (Backbone.VERSION === "0.9.10") { | |
| options.error(model, errorMessage, options); | |
| } else { | |
| options.error(errorMessage); | |
| } | |
| if (syncDfd) | |
| syncDfd.reject(errorMessage); | |
| } | |
| // add compatibility with $.ajax | |
| // always execute callback for success and error | |
| if (options && options.complete) options.complete(resp); | |
| return syncDfd && syncDfd.promise(); | |
| }; | |
| Backbone.ajaxSync = Backbone.sync; | |
| Backbone.getSyncMethod = function(model) { | |
| if(model.localStorage || (model.collection && model.collection.localStorage)) { | |
| return Backbone.localSync; | |
| } | |
| return Backbone.ajaxSync; | |
| }; | |
| // Override 'Backbone.sync' to default to localSync, | |
| // the original 'Backbone.sync' is still available in 'Backbone.ajaxSync' | |
| Backbone.sync = function(method, model, options) { | |
| return Backbone.getSyncMethod(model).apply(this, [method, model, options]); | |
| }; | |
| return Backbone.LocalStorage; | |
| })); | |
| ////////////////// | |
| $(function () { | |
| var Todo = Backbone.Model.extend({ | |
| defaults: function () { | |
| return { | |
| title: 'empty todo ...', | |
| order: Todos.nextOrder(), | |
| done: false | |
| }; | |
| }, | |
| toggle: function () { | |
| this.save({done: !this.get('done')}); | |
| } | |
| }); | |
| var TodoList = Backbone.Collection.extend({ | |
| model: Todo, | |
| localStorage: new Backbone.LocalStorage('todos-backbone'), | |
| done: function () { | |
| return this.where({done: true}); | |
| }, | |
| remaining: function () { | |
| return this.without.apply(this, this.done()); | |
| }, | |
| nextOrder: function () { | |
| if (!this.length) return 1; | |
| return this.last().get('order') + 1; | |
| }, | |
| comparator: 'order' | |
| }); | |
| var Todos = new TodoList(); | |
| var TodoView = Backbone.View.extend({ | |
| tagName: 'li', | |
| template: _.template($('#item-template').html()), | |
| events: { | |
| 'click .toggle': 'toggleDone', | |
| 'dbclick .view': 'edit', | |
| 'click a.destroy': 'clear', | |
| 'keypress .edit': 'updateOnEnter', | |
| 'blur .edit': 'close' | |
| }, | |
| initialize: function () { | |
| this.listenTo(this.model, 'change', this.render); | |
| this.listenTo(this.model, 'destroy', this.remove); | |
| }, | |
| render: function () { | |
| this.$el.html(this.template(this.model.toJSON())); | |
| this.$el.toggleClass('done', this.model.get('done')); | |
| this.input = this.$('edit'); | |
| return this; | |
| }, | |
| toggleDone: function () { | |
| this.model.toggle(); | |
| }, | |
| edit: function () { | |
| this.$el.addClass('editing'); | |
| this.input.focus(); | |
| }, | |
| close: function () { | |
| var value = this.input.val(); | |
| if (!value) { | |
| this.clear(); | |
| } else { | |
| this.model.save({title: value}); | |
| this.$el.removeClass('editing'); | |
| } | |
| }, | |
| updateOnEnter: function (e) { | |
| if (e.keyCode === 13) this.close(); | |
| }, | |
| clear: function () { | |
| this.model.destroy(); | |
| } | |
| }); | |
| var AppView = Backbone.View.extend({ | |
| el: $('#todoapp'), | |
| statsTemplate: _.template($('#stats-template').html()), | |
| events: { | |
| 'keypress #new-todo': 'createOnEnter', | |
| 'click #clear-completed': 'clearCompleted', | |
| 'click #toggle-all': 'toggleAllComplete' | |
| }, | |
| initialize: function () { | |
| this.input = this.$('#new-todo'); | |
| this.allCheckbox = this.$('#toggle-all')[0]; | |
| this.listenTo(Todos, 'add', this.addOne); | |
| this.listenTo(Todos, 'reset', this.addAll); | |
| this.listenTo(Todos, 'all', this.render); | |
| this.footer = this.$('footer'); | |
| this.main = $('#main'); | |
| Todos.fetch(); | |
| }, | |
| render: function () { | |
| var done = Todos.done().length; | |
| var remaining = Todos.remaining().length; | |
| if (Todos.length) { | |
| this.main.show(); | |
| this.footer.show(); | |
| this.footer.html(this.statsTemplate({done: done, remaining: remaining})); | |
| } else { | |
| this.main.hide(); | |
| this.footer.hide(); | |
| } | |
| this.allCheckbox.checked = !remaining; | |
| }, | |
| addOne: function (todo) { | |
| var view = new TodoView({model: todo}); | |
| this.$('#todo-list').append(view.render().el); | |
| }, | |
| addAll: function () { | |
| Todos.each(this.addOne, this); | |
| }, | |
| createOnEnter: function (e) { | |
| console.log('enter'); | |
| if (e.keyCode !== 13) return; | |
| if (!this.input.val()) return; | |
| Todos.create({title: this.input.val()}); | |
| this.input.val(''); | |
| }, | |
| clearCompleted: function () { | |
| _.invoke(Todos.done(), 'destroy'); | |
| return false; | |
| }, | |
| toggleAllComplete: function () { | |
| var done = this.allCheckbox.checked; | |
| Todos.each(function (todo) { todo.save({ 'done': done}); }); | |
| } | |
| }); | |
| var App = new AppView(); | |
| console.log(App.statsTemplate); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment