Commit f9fab087 authored by Boris Kozorovitzky's avatar Boris Kozorovitzky

Fixed various issues with this version.

Please refer to README.md for a complete list of changes.
parent fb7f0094
# Backbone.Marionette TodoMVC app # Backbone.Marionette TodoMVC app
[Backbone.Marionette](http://marionettejs.com) is a composite application library for Backbone.js that aims to simplify the construction of large scale JavaScript applications. It is a collection of common design and implementation patterns found in the applications that Derick Bailey has been building with Backbone, and includes various pieces inspired by composite application architectures, such as Microsoft's "Prism" framework. [Backbone.Marionette](http://marionettejs.com) is a composite
application library for Backbone.js that aims to simplify
the construction of large scale JavaScript applications.
It is a collection of common design and implementation patterns
found in the applications that I (Derick Bailey) have been building
with Backbone, and includes various pieces inspired by composite
application architectures, such as Microsoft's "Prism" framework.
This implementation of the application uses Marionette's module system. Variations using RequireJS and a more classic approach to JavaScript modules are also [available](https://github.com/marionettejs/backbone.marionette/wiki/Projects-and-websites-using-marionette). ## Key Benefits
* Scale applications out with modular, event driven architecture
* Sensible defaults, such as using Underscore templates for view rendering
* Easy to modify to make it work with your applicaton's specific needs
* Reduce boilerplate for views, with specialized view types
* Build on a modular architecture with an Application and modules that attach to it
* Compose your application's visuals at runtime, with Region and Layout
* Nested views and layouts within visual regions
* Built-in memory management and zombie killing in views, regions and layouts
* Built-in event clean up with the EventBinder
* Event-driven architecture with the EventAggregator
* Flexible, "as-needed" architecture allowing you to pick and choose what you need
* And much, much more
This implementation of the application uses Marionette's module system.
Variations using RequireJS and a more classic approach to JavaScript modules
[are also available here](https://github.com/marionettejs/backbone.marionette/wiki/Projects-and-websites-using-marionette).
\ No newline at end of file
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<script src="../../../assets/ie.js"></script> <script src="../../../assets/ie.js"></script>
<![endif]--> <![endif]-->
<script type="text/html" id="template-footer"> <script type="text/html" id="template-footer">
<span id="todo-count"><strong></strong> items left</span> <span id="todo-count"><strong></strong><span></span></span>
<ul id="filters"> <ul id="filters">
<li> <li>
<a href="#">All</a> <a href="#">All</a>
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<a href="#completed">Completed</a> <a href="#completed">Completed</a>
</li> </li>
</ul> </ul>
<button id="clear-completed">Clear completed</button> <button id="clear-completed"></button>
</script> </script>
<script type="text/html" id="template-header"> <script type="text/html" id="template-header">
<h1>todos</h1> <h1>todos</h1>
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
<script src="../../../assets/jquery.min.js"></script> <script src="../../../assets/jquery.min.js"></script>
<script src="../../../assets/lodash.min.js"></script> <script src="../../../assets/lodash.min.js"></script>
<script src="js/lib/backbone.js"></script> <script src="js/lib/backbone.js"></script>
<script src="js/lib/backbone-localStorage.js"></script> <script src="js/lib/backbone.localStorage.js"></script>
<script src="js/lib/backbone.marionette.js"></script> <script src="js/lib/backbone.marionette.js"></script>
<!-- application --> <!-- application -->
<script src="js/TodoMVC.js"></script> <script src="js/TodoMVC.js"></script>
......
TodoMVC.module('Layout', function(Layout, App, Backbone, Marionette, $, _) { /*global TodoMVC */
'use strict';
TodoMVC.module('Layout', function (Layout, App, Backbone) {
// Layout Header View // Layout Header View
// ------------------ // ------------------
...@@ -12,15 +15,15 @@ TodoMVC.module('Layout', function(Layout, App, Backbone, Marionette, $, _) { ...@@ -12,15 +15,15 @@ TodoMVC.module('Layout', function(Layout, App, Backbone, Marionette, $, _) {
input: '#new-todo' input: '#new-todo'
}, },
events : { events: {
'keypress #new-todo': 'onInputKeypress' 'keypress #new-todo': 'onInputKeypress'
}, },
onInputKeypress: function(e) { onInputKeypress: function (e) {
var ENTER_KEY = 13; var ENTER_KEY = 13,
var todoText = this.ui.input.val().trim(); todoText = this.ui.input.val().trim();
if ( e.which === ENTER_KEY && todoText ) { if (e.which === ENTER_KEY && todoText) {
this.collection.create({ this.collection.create({
title: todoText title: todoText
}); });
...@@ -39,36 +42,50 @@ TodoMVC.module('Layout', function(Layout, App, Backbone, Marionette, $, _) { ...@@ -39,36 +42,50 @@ TodoMVC.module('Layout', function(Layout, App, Backbone, Marionette, $, _) {
// point to jQuery selected objects // point to jQuery selected objects
ui: { ui: {
count: '#todo-count strong', count: '#todo-count strong',
filters: '#filters a' itemsString: '#todo-count span',
filters: '#filters a',
clearCompleted: '#clear-completed'
}, },
events: { events: {
'click #clear-completed': 'onClearClick' 'click #clear-completed': 'onClearClick'
}, },
initialize : function() { initialize: function () {
this.bindTo(App.vent, 'todoList:filter', this.updateFilterSelection, this); this.listenTo(App.vent, 'todoList:filter', this.updateFilterSelection, this);
this.bindTo(this.collection, 'all', this.updateCount, this); this.listenTo(this.collection, 'all', this.updateCount, this);
}, },
onRender: function() { onRender: function () {
this.updateCount(); this.updateCount();
}, },
updateCount: function() { updateCount: function () {
var count = this.collection.getActive().length; var count = this.collection.getActive().length,
length = this.collection.length,
completed = length - count;
this.ui.count.html(count); this.ui.count.html(count);
this.$el.parent().toggle(count > 0); this.ui.itemsString.html(' ' + (count === 1 ? 'item' : 'items') + ' left');
this.$el.parent().toggle(length > 0);
if (completed > 0) {
this.ui.clearCompleted.show();
this.ui.clearCompleted.html('Clear completed (' + completed + ')');
} else {
this.ui.clearCompleted.hide();
}
}, },
updateFilterSelection : function(filter) { updateFilterSelection: function (filter) {
this.ui.filters this.ui.filters
.removeClass('selected') .removeClass('selected')
.filter('[href="#' + filter + '"]') .filter('[href="#' + filter + '"]')
.addClass('selected'); .addClass('selected');
}, },
onClearClick: function() { onClearClick: function () {
var completed = this.collection.getCompleted(); var completed = this.collection.getCompleted();
completed.forEach(function destroy(todo) { completed.forEach(function destroy(todo) {
todo.destroy(); todo.destroy();
......
TodoMVC.module('TodoList.Views', function(Views, App, Backbone, Marionette, $, _) { /*global TodoMVC */
'use strict';
TodoMVC.module('TodoList.Views', function (Views, App, Backbone, Marionette, $) {
// Todo List Item View // Todo List Item View
// ------------------- // -------------------
...@@ -8,55 +11,70 @@ TodoMVC.module('TodoList.Views', function(Views, App, Backbone, Marionette, $, _ ...@@ -8,55 +11,70 @@ TodoMVC.module('TodoList.Views', function(Views, App, Backbone, Marionette, $, _
Views.ItemView = Marionette.ItemView.extend({ Views.ItemView = Marionette.ItemView.extend({
tagName: 'li', tagName: 'li',
template: '#template-todoItemView', template: '#template-todoItemView',
ui: { ui: {
edit: '.edit' edit: '.edit'
}, },
events : { events: {
'click .destroy': 'destroy', 'click .destroy': 'destroy',
'dblclick label': 'onEditClick', 'dblclick label': 'onEditClick',
'keypress .edit': 'onEditKeypress', 'keydown .edit': 'onEditKeypress',
'click .toggle' : 'toggle' 'focusout .edit': 'onEditFocusout',
}, 'click .toggle': 'toggle'
},
initialize: function() {
this.bindTo(this.model, 'change', this.render, this); initialize: function () {
}, this.listenTo(this.model, 'change', this.render, this);
},
onRender: function() {
this.$el.removeClass('active completed'); onRender: function () {
this.$el.removeClass('active completed');
if (this.model.get('completed')) {
this.$el.addClass('completed'); if (this.model.get('completed')) {
} else { this.$el.addClass('completed');
this.$el.addClass('active'); } else {
} this.$el.addClass('active');
}, }
},
destroy: function() {
this.model.destroy(); destroy: function () {
}, this.model.destroy();
},
toggle: function() {
this.model.toggle().save(); toggle: function () {
}, this.model.toggle().save();
},
onEditClick: function() {
this.$el.addClass('editing'); onEditClick: function () {
this.ui.edit.focus(); this.$el.addClass('editing');
}, this.ui.edit.focus();
this.ui.edit.val(this.ui.edit.val());
onEditKeypress: function(e) { },
var ENTER_KEY = 13;
var todoText = this.ui.edit.val().trim(); onEditFocusout: function () {
var todoText = this.ui.edit.val().trim();
if ( e.which === ENTER_KEY && todoText ) { if (todoText) {
this.model.set('title', todoText).save(); this.model.set('title', todoText).save();
this.$el.removeClass('editing'); this.$el.removeClass('editing');
} } else {
this.destroy();
}
},
onEditKeypress: function (e) {
var ENTER_KEY = 13, ESC_KEY = 27;
if (e.which === ENTER_KEY) {
this.onEditFocusout();
return;
} }
if (e.which === ESC_KEY) {
this.$el.removeClass('editing');
}
}
}); });
// Item List View // Item List View
...@@ -67,43 +85,43 @@ TodoMVC.module('TodoList.Views', function(Views, App, Backbone, Marionette, $, _ ...@@ -67,43 +85,43 @@ TodoMVC.module('TodoList.Views', function(Views, App, Backbone, Marionette, $, _
Views.ListView = Backbone.Marionette.CompositeView.extend({ Views.ListView = Backbone.Marionette.CompositeView.extend({
template: '#template-todoListCompositeView', template: '#template-todoListCompositeView',
itemView: Views.ItemView, itemView: Views.ItemView,
itemViewContainer: '#todo-list', itemViewContainer: '#todo-list',
ui: { ui: {
toggle: '#toggle-all' toggle: '#toggle-all'
}, },
events : { events: {
'click #toggle-all': 'onToggleAllClick' 'click #toggle-all': 'onToggleAllClick'
}, },
initialize: function() { initialize: function () {
this.bindTo(this.collection, 'all', this.update, this); this.listenTo(this.collection, 'all', this.update, this);
}, },
onRender: function() { onRender: function () {
this.update(); this.update();
}, },
update: function() { update: function () {
function reduceCompleted(left, right) { function reduceCompleted(left, right) {
return left && right.get('completed'); return left && right.get('completed');
} }
var allCompleted = this.collection.reduce(reduceCompleted,true); var allCompleted = this.collection.reduce(reduceCompleted, true);
this.ui.toggle.prop('checked', allCompleted); this.ui.toggle.prop('checked', allCompleted);
this.$el.parent().toggle(!!this.collection.length); this.$el.parent().toggle(!!this.collection.length);
}, },
onToggleAllClick: function(e) { onToggleAllClick: function (e) {
var isChecked = e.currentTarget.checked; var isChecked = e.currentTarget.checked;
this.collection.each(function(todo){ this.collection.each(function (todo) {
todo.save({'completed': isChecked}); todo.save({ 'completed': isChecked });
}); });
} }
}); });
// Application Event Handlers // Application Event Handlers
...@@ -112,7 +130,7 @@ TodoMVC.module('TodoList.Views', function(Views, App, Backbone, Marionette, $, _ ...@@ -112,7 +130,7 @@ TodoMVC.module('TodoList.Views', function(Views, App, Backbone, Marionette, $, _
// Handler for filtering the list of items by showing and // Handler for filtering the list of items by showing and
// hiding through the use of various CSS classes // hiding through the use of various CSS classes
App.vent.on('todoList:filter', function(filter) { App.vent.on('todoList:filter', function (filter) {
filter = filter || 'all'; filter = filter || 'all';
$('#todoapp').attr('class', 'filter-' + filter); $('#todoapp').attr('class', 'filter-' + filter);
}); });
......
TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _) { /*global TodoMVC */
'use strict';
TodoMVC.module('TodoList', function (TodoList, App, Backbone, Marionette, $, _) {
// TodoList Router // TodoList Router
// --------------- // ---------------
...@@ -17,7 +20,7 @@ TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _) { ...@@ -17,7 +20,7 @@ TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _) {
// Control the workflow and logic that exists at the application // Control the workflow and logic that exists at the application
// level, above the implementation detail of views and models // level, above the implementation detail of views and models
TodoList.Controller = function() { TodoList.Controller = function () {
this.todoList = new App.Todos.TodoList(); this.todoList = new App.Todos.TodoList();
}; };
...@@ -25,7 +28,7 @@ TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _) { ...@@ -25,7 +28,7 @@ TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _) {
// Start the app by showing the appropriate views // Start the app by showing the appropriate views
// and fetching the list of todo items, if there are any // and fetching the list of todo items, if there are any
start: function(){ start: function () {
this.showHeader(this.todoList); this.showHeader(this.todoList);
this.showFooter(this.todoList); this.showFooter(this.todoList);
this.showTodoList(this.todoList); this.showTodoList(this.todoList);
...@@ -33,28 +36,28 @@ TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _) { ...@@ -33,28 +36,28 @@ TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _) {
this.todoList.fetch(); this.todoList.fetch();
}, },
showHeader: function(todoList) { showHeader: function (todoList) {
var header = new App.Layout.Header({ var header = new App.Layout.Header({
collection: todoList collection: todoList
}); });
App.header.show(header); App.header.show(header);
}, },
showFooter: function(todoList) { showFooter: function (todoList) {
var footer = new App.Layout.Footer({ var footer = new App.Layout.Footer({
collection: todoList collection: todoList
}); });
App.footer.show(footer); App.footer.show(footer);
}, },
showTodoList: function(todoList) { showTodoList: function (todoList) {
App.main.show(new TodoList.Views.ListView({ App.main.show(new TodoList.Views.ListView({
collection : todoList collection: todoList
})); }));
}, },
// Set the filter to show complete or all items // Set the filter to show complete or all items
filterItems: function(filter) { filterItems: function (filter) {
App.vent.trigger('todoList:filter', filter.trim() || ''); App.vent.trigger('todoList:filter', filter.trim() || '');
} }
}); });
...@@ -66,9 +69,9 @@ TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _) { ...@@ -66,9 +69,9 @@ TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _) {
// when the the application is started, pulling in all of the // when the the application is started, pulling in all of the
// existing Todo items and displaying them. // existing Todo items and displaying them.
TodoList.addInitializer(function() { TodoList.addInitializer(function () {
var controller = new TodoList.Controller(); var controller = new TodoList.Controller();
new TodoList.Router({ controller.router = new TodoList.Router({
controller: controller controller: controller
}); });
......
TodoMVC.module('Todos', function(Todos, App, Backbone, Marionette, $, _) { /*global TodoMVC */
'use strict';
TodoMVC.module('Todos', function (Todos, App, Backbone) {
// Todo Model // Todo Model
// ---------- // ----------
Todos.Todo = Backbone.Model.extend({ Todos.Todo = Backbone.Model.extend({
localStorage: new Backbone.LocalStorage('todos-backbone-marionettejs'),
defaults: { defaults: {
title: '', title: '',
...@@ -12,17 +14,17 @@ TodoMVC.module('Todos', function(Todos, App, Backbone, Marionette, $, _) { ...@@ -12,17 +14,17 @@ TodoMVC.module('Todos', function(Todos, App, Backbone, Marionette, $, _) {
created: 0 created: 0
}, },
initialize: function() { initialize: function () {
if (this.isNew()) { if (this.isNew()) {
this.set('created', Date.now()); this.set('created', Date.now());
} }
}, },
toggle: function() { toggle: function () {
return this.set('completed', !this.isCompleted()); return this.set('completed', !this.isCompleted());
}, },
isCompleted: function() { isCompleted: function () {
return this.get('completed'); return this.get('completed');
} }
}); });
...@@ -33,21 +35,21 @@ TodoMVC.module('Todos', function(Todos, App, Backbone, Marionette, $, _) { ...@@ -33,21 +35,21 @@ TodoMVC.module('Todos', function(Todos, App, Backbone, Marionette, $, _) {
Todos.TodoList = Backbone.Collection.extend({ Todos.TodoList = Backbone.Collection.extend({
model: Todos.Todo, model: Todos.Todo,
localStorage: new Backbone.LocalStorage('todos-backbone'), localStorage: new Backbone.LocalStorage('todos-backbone-marionette'),
getCompleted: function() { getCompleted: function () {
return this.filter(this._isCompleted); return this.filter(this._isCompleted);
}, },
getActive: function() { getActive: function () {
return this.reject(this._isCompleted); return this.reject(this._isCompleted);
}, },
comparator: function(todo) { comparator: function (todo) {
return todo.get('created'); return todo.get('created');
}, },
_isCompleted: function(todo){ _isCompleted: function (todo) {
return todo.isCompleted(); return todo.isCompleted();
} }
}); });
......
/*global Backbone */
'use strict';
var TodoMVC = new Backbone.Marionette.Application(); var TodoMVC = new Backbone.Marionette.Application();
TodoMVC.addRegions({ TodoMVC.addRegions({
...@@ -6,6 +9,6 @@ TodoMVC.addRegions({ ...@@ -6,6 +9,6 @@ TodoMVC.addRegions({
footer: '#footer' footer: '#footer'
}); });
TodoMVC.on('initialize:after', function() { TodoMVC.on('initialize:after', function () {
Backbone.history.start(); Backbone.history.start();
}); });
/**
* Backbone localStorage Adapter
* https://github.com/jeromegn/Backbone.localStorage
*/
(function() {
// 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
var _ = this._;
var Backbone = this.Backbone;
// 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 model.toJSON();
},
// 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 model.toJSON();
},
// Retrieve a model from `this.data` by id.
find: function(model) {
return JSON.parse(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 JSON.parse(this.localStorage().getItem(this.name+"-"+id));}, this)
.compact()
.value();
},
// Delete a model from `this.data`, returning it.
destroy: function(model) {
this.localStorage().removeItem(this.name+"-"+model.id);
this.records = _.reject(this.records, function(record_id){return record_id == model.id.toString();});
this.save();
return model;
},
localStorage: function() {
return localStorage;
}
});
// 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, error) {
var store = model.localStorage || model.collection.localStorage;
// Backwards compatibility with Backbone <= 0.3.3
if (typeof options == 'function') {
options = {
success: options,
error: error
};
}
var resp;
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;
}
if (resp) {
options.success(resp);
} else {
options.error("Record not found");
}
};
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, error) {
return Backbone.getSyncMethod(model).apply(this, [method, model, options, error]);
};
})();
/**
* Backbone localStorage Adapter
* Version 1.1.0
*
* https://github.com/jeromegn/Backbone.localStorage
*/
(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 are undefined.
return factory(_ || root._, Backbone || root.Backbone);
});
} else {
// RequireJS isn't being used. Assume underscore and backbone are 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) {
model.trigger("sync", model, resp, options);
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";
model.trigger("error", model, errorMessage, options);
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;
}));
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment