From 7afd43c0ef110a9aafdcefb12453daf8f7dfde77 Mon Sep 17 00:00:00 2001 From: Damien Holzapfel Date: Wed, 27 Apr 2011 10:38:13 -0700 Subject: [PATCH 01/19] Reference calls variable in list variable definition in Backbone.Events.bind --- backbone.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backbone.js b/backbone.js index bec4de009..3045a8c45 100644 --- a/backbone.js +++ b/backbone.js @@ -70,7 +70,7 @@ // Passing `"all"` will bind the callback to all events fired. bind : function(ev, callback) { var calls = this._callbacks || (this._callbacks = {}); - var list = this._callbacks[ev] || (this._callbacks[ev] = []); + var list = calls[ev] || (calls[ev] = []); list.push(callback); return this; }, From 9d5308b1ab76f8cce34d945a9afb255e720da977 Mon Sep 17 00:00:00 2001 From: Corban Brook Date: Thu, 5 May 2011 15:15:16 -0400 Subject: [PATCH 02/19] Pass options to the coll.add call in Collection::create so that it can work silently. --- backbone.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backbone.js b/backbone.js index 7ec5d484e..11bfea78a 100644 --- a/backbone.js +++ b/backbone.js @@ -536,7 +536,7 @@ } var success = options.success; options.success = function(nextModel, resp, xhr) { - coll.add(nextModel); + coll.add(nextModel, options); if (success) success(nextModel, resp, xhr); }; model.save(null, options); From 82c288c91e9c3781e3afeedf1aa30626e6f9a788 Mon Sep 17 00:00:00 2001 From: Francis Date: Sat, 7 May 2011 22:47:35 -0700 Subject: [PATCH 03/19] Following recommendations from the OWASP https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet Should be tested with http://ha.ckers.org/xss.html Make sure your pages are utf8! --- backbone.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backbone.js b/backbone.js index 7ec5d484e..795b48b33 100644 --- a/backbone.js +++ b/backbone.js @@ -1094,7 +1094,7 @@ // Helper function to escape a string for HTML rendering. var escapeHTML = function(string) { - return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(//g, '>').replace(/"/g, '"'); + return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); }; }).call(this); From cf3215139acc86a54e94f039a0dbf627b49a5e14 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 26 May 2011 10:54:28 -0400 Subject: [PATCH 04/19] initial draft of a 0.5.0 branch --- backbone.js | 140 ++++++++++++++++++++---------- examples/todos/todos.js | 2 +- index.html | 44 ++++++---- package.json | 2 +- test/collection.js | 32 ++++--- test/{controller.js => router.js} | 53 +++++------ test/test.html | 2 +- 7 files changed, 170 insertions(+), 105 deletions(-) rename test/{controller.js => router.js} (58%) diff --git a/backbone.js b/backbone.js index f769caa79..706c481e0 100644 --- a/backbone.js +++ b/backbone.js @@ -1,4 +1,4 @@ -// Backbone.js 0.3.3 +// Backbone.js 0.5.0-pre // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. // Backbone may be freely distributed under the MIT license. // For all details and documentation: @@ -25,7 +25,7 @@ } // Current version of the library. Keep in sync with `package.json`. - Backbone.VERSION = '0.3.3'; + Backbone.VERSION = '0.5.0-pre'; // Require Underscore, if we're on the server, and it's not already present. var _ = root._; @@ -416,7 +416,7 @@ } _.bindAll(this, '_onModelEvent', '_removeReference'); this._reset(); - if (models) this.refresh(models, {silent: true}); + if (models) this.reset(models, {silent: true}); this.initialize(models, options); }; @@ -485,7 +485,7 @@ options || (options = {}); if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); this.models = this.sortBy(this.comparator); - if (!options.silent) this.trigger('refresh', this, options); + if (!options.silent) this.trigger('reset', this, options); return this; }, @@ -497,13 +497,13 @@ // When you have more items than you want to add or remove individually, // you can refresh the entire set with a new list of models, without firing // any `added` or `removed` events. Fires `refresh` when finished. - refresh : function(models, options) { + reset : function(models, options) { models || (models = []); options || (options = {}); this.each(this._removeReference); this._reset(); this.add(models, {silent: true}); - if (!options.silent) this.trigger('refresh', this, options); + if (!options.silent) this.trigger('reset', this, options); return this; }, @@ -515,7 +515,7 @@ var collection = this; var success = options.success; options.success = function(resp, status, xhr) { - collection[options.add ? 'add' : 'refresh'](collection.parse(resp, xhr), options); + collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options); if (success) success(collection, resp); }; options.error = wrapError(options.error, collection, options); @@ -578,7 +578,8 @@ if (!model.collection) { model.collection = this; } - var index = this.comparator ? this.sortedIndex(model, this.comparator) : this.length; + var index = this.comparator ? this.sortedIndex(model, this.comparator) : + options.at != null ? options.at : this.length; this.models.splice(index, 0, model); model.bind('all', this._onModelEvent); this.length++; @@ -640,12 +641,12 @@ }; }); - // Backbone.Controller + // Backbone.Router // ------------------- - // Controllers map faux-URLs to actions, and fire events when routes are + // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. - Backbone.Controller = function(options) { + Backbone.Router = function(options) { options || (options = {}); if (options.routes) this.routes = options.routes; this._bindRoutes(); @@ -658,8 +659,8 @@ var splatParam = /\*([\w\d]+)/g; var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g; - // Set up all inheritable **Backbone.Controller** properties and methods. - _.extend(Backbone.Controller.prototype, Backbone.Events, { + // Set up all inheritable **Backbone.Router** properties and methods. + _.extend(Backbone.Router.prototype, Backbone.Events, { // Initialize is an empty function by default. Override it with your own // initialization logic. @@ -674,8 +675,8 @@ route : function(route, name, callback) { Backbone.history || (Backbone.history = new Backbone.History); if (!_.isRegExp(route)) route = this._routeToRegExp(route); - Backbone.history.route(route, _.bind(function(hash) { - var args = this._extractParameters(route, hash); + Backbone.history.route(route, _.bind(function(fragment) { + var args = this._extractParameters(route, fragment); callback.apply(this, args); this.trigger.apply(this, ['route:' + name].concat(args)); }, this)); @@ -683,8 +684,16 @@ // Simple proxy to `Backbone.history` to save a fragment into the history, // without triggering routes. - saveLocation : function(hash) { - Backbone.history.saveLocation(hash); + saveLocation : function(fragment) { + Backbone.history.saveLocation(fragment); + }, + + // Simple proxy to `Backbone.history` to both save a fragment into the + // history and to then load the route at that fragment. Used in place + // of settings `window.location.hash` when using `window.history.pushState`. + loadUrl : function(fragment) { + Backbone.history.saveLocation(fragment); + Backbone.history.loadUrl(); }, // Bind all defined routes to `Backbone.history`. We have to reverse the @@ -712,8 +721,8 @@ // Given a route, and a URL fragment that it matches, return the array of // extracted parameters. - _extractParameters : function(route, hash) { - return route.exec(hash).slice(1); + _extractParameters : function(route, fragment) { + return route.exec(fragment).slice(1); } }); @@ -721,7 +730,7 @@ // Backbone.History // ---------------- - // Handles cross-browser history management, based on URL hashes. If the + // Handles cross-browser history management, based on URL fragments. If the // browser does not support `onhashchange`, falls back to polling. Backbone.History = function() { this.handlers = []; @@ -745,32 +754,54 @@ interval: 50, // Get the cross-browser normalized URL fragment. - getHash : function(loc) { - return (loc || window.location).hash.replace(hashStrip, ''); + getFragment : function(fragment, forcePushState) { + if (!fragment) { + if (this._hasPushState || forcePushState) { + fragment = window.location.pathname; + var search = window.location.search; + if (search) fragment += search; + if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length); + } else { + fragment = window.location.hash; + } + } + return fragment.replace(hashStrip, ''); }, // Start the hash change handling, returning `true` if the current URL matches // an existing route, and `false` otherwise. - start : function() { + start : function(options) { if (historyStarted) throw new Error("Backbone.history has already been started"); - var hash = this.getHash(); + this.options = _.extend({}, {root: '/'}, options); + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState); + var fragment = this.getFragment(); var docMode = document.documentMode; var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); if (oldIE) { this.iframe = $('