todos.js | |
---|---|
An example Backbone application contributed by Jérôme Gravel-Niquet. This demo uses a simple LocalStorage adapter to persist Backbone models within your browser. | |
Load the application once the DOM is ready, using | $(function(){ |
Todo Model | |
Our basic Todo model has | window.Todo = Backbone.Model.extend({ |
Default attributes for a todo item. | defaults: function() {
return {
done: false,
order: Todos.nextOrder()
};
}, |
Toggle the | toggle: function() {
this.save({done: !this.get("done")});
}
}); |
Todo Collection | |
The collection of todos is backed by localStorage instead of a remote server. | window.TodoList = Backbone.Collection.extend({ |
Reference to this collection's model. | model: Todo, |
Save all of the todo items under the | localStorage: new Store("todos"), |
Filter down the list of all todo items that are finished. | done: function() {
return this.filter(function(todo){ return todo.get('done'); });
}, |
Filter down the list to only todo items that are still not finished. | remaining: function() {
return this.without.apply(this, this.done());
}, |
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. | nextOrder: function() {
if (!this.length) return 1;
return this.last().get('order') + 1;
}, |
Todos are sorted by their original insertion order. | comparator: function(todo) {
return todo.get('order');
}
}); |
Create our global collection of Todos. | window.Todos = new TodoList; |
Todo Item View | |
The DOM element for a todo item... | window.TodoView = Backbone.View.extend({ |
... is a list tag. | tagName: "li", |
Cache the template function for a single item. | template: _.template($('#item-template').html()), |
The DOM events specific to an item. | events: {
"click .check" : "toggleDone",
"dblclick div.todo-text" : "edit",
"click span.todo-destroy" : "clear",
"keypress .todo-input" : "updateOnEnter"
}, |
The TodoView listens for changes to its model, re-rendering. | initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
}, |
Re-render the contents of the todo item. | render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setText();
return this;
}, |
To avoid XSS (not that it would be harmful in this particular app),
we use | setText: function() {
var text = this.model.get('text');
this.$('.todo-text').text(text);
this.input = this.$('.todo-input');
this.input.bind('blur', _.bind(this.close, this)).val(text);
}, |
Toggle the | toggleDone: function() {
this.model.toggle();
}, |
Switch this view into | edit: function() {
$(this.el).addClass("editing");
this.input.focus();
}, |
Close the | close: function() {
this.model.save({text: this.input.val()});
$(this.el).removeClass("editing");
}, |
If you hit | updateOnEnter: function(e) {
if (e.keyCode == 13) this.close();
}, |
Remove this view from the DOM. | remove: function() {
$(this.el).remove();
}, |
Remove the item, destroy the model. | clear: function() {
this.model.destroy();
}
}); |
The Application | |
Our overall AppView is the top-level piece of UI. | window.AppView = Backbone.View.extend({ |
Instead of generating a new element, bind to the existing skeleton of the App already present in the HTML. | el: $("#todoapp"), |
Our template for the line of statistics at the bottom of the app. | statsTemplate: _.template($('#stats-template').html()), |
Delegated events for creating new items, and clearing completed ones. | events: {
"keypress #new-todo": "createOnEnter",
"keyup #new-todo": "showTooltip",
"click .todo-clear a": "clearCompleted"
}, |
At initialization we bind to the relevant events on the | initialize: function() {
this.input = this.$("#new-todo");
Todos.bind('add', this.addOne, this);
Todos.bind('reset', this.addAll, this);
Todos.bind('all', this.render, this);
Todos.fetch();
}, |
Re-rendering the App just means refreshing the statistics -- the rest of the app doesn't change. | render: function() {
this.$('#todo-stats').html(this.statsTemplate({
total: Todos.length,
done: Todos.done().length,
remaining: Todos.remaining().length
}));
}, |
Add a single todo item to the list by creating a view for it, and
appending its element to the | addOne: function(todo) {
var view = new TodoView({model: todo});
$("#todo-list").append(view.render().el);
}, |
Add all items in the Todos collection at once. | addAll: function() {
Todos.each(this.addOne);
}, |
If you hit return in the main input field, and there is text to save, create new Todo model persisting it to localStorage. | createOnEnter: function(e) {
var text = this.input.val();
if (!text || e.keyCode != 13) return;
Todos.create({text: text});
this.input.val('');
}, |
Clear all done todo items, destroying their models. | clearCompleted: function() {
_.each(Todos.done(), function(todo){ todo.destroy(); });
return false;
}, |
Lazily show the tooltip that tells you to press | showTooltip: function(e) {
var tooltip = this.$(".ui-tooltip-top");
var val = this.input.val();
tooltip.fadeOut();
if (this.tooltipTimeout) clearTimeout(this.tooltipTimeout);
if (val == '' || val == this.input.attr('placeholder')) return;
var show = function(){ tooltip.show().fadeIn(); };
this.tooltipTimeout = _.delay(show, 1000);
}
}); |
Finally, we kick things off by creating the App. | window.App = new AppView;
});
|