From 6ecee63738ca82282fde318fdb4d51c32da9df9a Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Wed, 13 May 2015 19:26:20 -0400
Subject: [PATCH 001/235] Juggle context param with event maps
When using event maps, it's a little nicer to specify the `context`
parameter as the second argument. This keeps backwards compatibility,
though the feature wasn't documented.
```javascript
obj.on({
a: method,
b: other
}, context);
```
When you explicitly pass a `context`, we don't override.
```javascript
obj.on({
a: function() { ok(this === context); },
}, otherContext, context);
```
---
backbone.js | 1 +
test/events.js | 18 ++++++++++++++++++
2 files changed, 19 insertions(+)
diff --git a/backbone.js b/backbone.js
index 8ebdac93..071a21db 100644
--- a/backbone.js
+++ b/backbone.js
@@ -96,6 +96,7 @@
var i = 0, names;
if (name && typeof name === 'object') {
// Handle event maps.
+ if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
for (names = _.keys(name); i < names.length ; i++) {
memo = iteratee(memo, names[i], name[names[i]], opts);
}
diff --git a/test/events.js b/test/events.js
index 017cf47f..a2730bc0 100644
--- a/test/events.js
+++ b/test/events.js
@@ -66,6 +66,24 @@
equal(obj.counter, 5);
});
+ test("binding and trigger with event maps context", 2, function() {
+ var obj = { counter: 0 };
+ var context = {};
+ _.extend(obj, Backbone.Events);
+
+ obj.on({
+ a: function() {
+ strictEqual(this, context, 'defaults `context` to `callback` param');
+ }
+ }, context).trigger('a');
+
+ obj.off().on({
+ a: function() {
+ strictEqual(this, context, 'will not override explicit `context` param');
+ }
+ }, this, context).trigger('a');
+ });
+
test("listenTo and stopListening", 1, function() {
var a = _.extend({}, Backbone.Events);
var b = _.extend({}, Backbone.Events);
From 8b0d82ec263f41d0ee3aba35d561d3c8c1227d49 Mon Sep 17 00:00:00 2001
From: Graeme Yeates
Date: Wed, 13 May 2015 23:49:04 -0400
Subject: [PATCH 002/235] Update documented underscore dependency
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index be7d0d63..af95db51 100644
--- a/index.html
+++ b/index.html
@@ -600,7 +600,7 @@
Backbone's only hard dependency is
- Underscore.js ( >= 1.5.0).
+ Underscore.js ( >= 1.7.0).
For RESTful persistence and DOM manipulation with Backbone.View,
include jQuery ( >= 1.11.0), and
json2.js for older
From 27b2d4370a0d56902bc7c8ae9c98436a09058a86 Mon Sep 17 00:00:00 2001
From: Alexey
Date: Fri, 15 May 2015 14:56:24 +0300
Subject: [PATCH 003/235] Typo @ index.html
movels -> models
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index af95db51..aa5391fb 100644
--- a/index.html
+++ b/index.html
@@ -999,7 +999,7 @@ view.stopListening(model);
@@ -1928,7 +1929,7 @@ ships.add([
them. Each model can be a Model instance, an id string or a JS
object, any value acceptable as the id argument of
collection.get.
- Fires a "remove" event for each model, and a single
+ Fires a "remove" event for each model, and a single
"update" event afterwards.
The model's index before removal is available to listeners as
options.index.
@@ -4225,8 +4226,8 @@ ActiveRecord::Base.include_root_in_json = false
Backbone will automatically try to load jQuery for you.
- Added an "update" event that triggers after any amount of
- models are added or removed from a collection. Handy to re-render lists
+ Added an "update" event that triggers after any amount of
+ models are added or removed from a collection. Handy to re-render lists
of things without debouncing.
From bbb43a9cc5da07ec3131f97f74cfeb50aca36253 Mon Sep 17 00:00:00 2001
From: Adam Krebs
Date: Fri, 15 May 2015 11:30:15 -0400
Subject: [PATCH 005/235] Document Collection#modelId
---
index.html | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/index.html b/index.html
index b9b79bfc..537346a8 100644
--- a/index.html
+++ b/index.html
@@ -377,6 +377,7 @@
- – extend
- – model
+ - – modelId
- – constructor / initialize
- – models
- – toJSON
@@ -1769,6 +1770,36 @@ var Library = Backbone.Collection.extend({
}
}
+});
+
+
+
+ collection.modelId
+
+ Override this method to specify the attribute the collection will use to
+ refer to its models in collection.get.
+
+ By default returns the idAttribute of the collection's model
+ class or failing that, 'id'. If your collection uses pollymorphic
+ models and those models have an idAttribute other than id
+ you must override this method with your own custom logic.
+
+
+
+var Library = Backbone.Collection.extend({
+
+ model: function(attrs, options) {
+ if (condition) {
+ return new PublicDocument(attrs, options);
+ } else {
+ return new PrivateDocument(attrs, options);
+ }
+ },
+
+ modelId: function(attrs) {
+ return attrs.private ? 'private_id' : 'public_id';
+ }
+
});
From 4cf2274a2c3041c755e1dddcdebab3406abd896a Mon Sep 17 00:00:00 2001
From: Andrew Humphreys
Date: Sat, 16 May 2015 14:13:24 +0100
Subject: [PATCH 006/235] fix opening and closing iframe doc for jsdom
---
backbone.js | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/backbone.js b/backbone.js
index 1dfbd9c5..78494cb7 100644
--- a/backbone.js
+++ b/backbone.js
@@ -1647,14 +1647,15 @@
// support the `hashchange` event, HTML5 history, or the user wants
// `hashChange` but not `pushState`.
if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
- var iframe = document.createElement('iframe');
- iframe.src = 'javascript:0';
- iframe.style.display = 'none';
- iframe.tabIndex = -1;
+ this.iframeElement = document.createElement('iframe');
+ this.iframeElement.src = 'javascript:0';
+ this.iframeElement.style.display = 'none';
+ this.iframeElement.tabIndex = -1;
var body = document.body;
// Using `appendChild` will throw on IE < 9 if the document is not ready.
- this.iframe = body.insertBefore(iframe, body.firstChild).contentWindow;
- this.iframe.document.open().close();
+ this.iframe = body.insertBefore(this.iframeElement, body.firstChild).contentWindow;
+ this.iframe.document.open();
+ this.iframe.document.close();
this.iframe.location.hash = '#' + this.fragment;
}
@@ -1693,8 +1694,9 @@
// Clean up the iframe if necessary.
if (this.iframe) {
- document.body.removeChild(this.iframe.frameElement);
+ document.body.removeChild(this.iframeElement);
this.iframe = null;
+ this.iframeElement = null;
}
// Some environments will throw when clearing an undefined interval.
@@ -1778,7 +1780,11 @@
// Opening and closing the iframe tricks IE7 and earlier to push a
// history entry on hash-tag change. When replace is true, we don't
// want this.
- if (!options.replace) this.iframe.document.open().close();
+ if (!options.replace) {
+ this.iframe.document.open();
+ this.iframe.document.close();
+ }
+
this._updateHash(this.iframe.location, fragment, options.replace);
}
From dced7c89d1dee20c0df840ccc6d15332d190c9b2 Mon Sep 17 00:00:00 2001
From: Andrew Humphreys
Date: Thu, 21 May 2015 20:44:25 +0100
Subject: [PATCH 007/235] use local iframe window to open/close
---
backbone.js | 31 ++++++++++++++++---------------
1 file changed, 16 insertions(+), 15 deletions(-)
diff --git a/backbone.js b/backbone.js
index 78494cb7..7a79b7c9 100644
--- a/backbone.js
+++ b/backbone.js
@@ -1647,16 +1647,16 @@
// support the `hashchange` event, HTML5 history, or the user wants
// `hashChange` but not `pushState`.
if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
- this.iframeElement = document.createElement('iframe');
- this.iframeElement.src = 'javascript:0';
- this.iframeElement.style.display = 'none';
- this.iframeElement.tabIndex = -1;
+ this.iframe = document.createElement('iframe');
+ this.iframe.src = 'javascript:0';
+ this.iframe.style.display = 'none';
+ this.iframe.tabIndex = -1;
var body = document.body;
// Using `appendChild` will throw on IE < 9 if the document is not ready.
- this.iframe = body.insertBefore(this.iframeElement, body.firstChild).contentWindow;
- this.iframe.document.open();
- this.iframe.document.close();
- this.iframe.location.hash = '#' + this.fragment;
+ var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
+ iWindow.document.open();
+ iWindow.document.close();
+ iWindow.location.hash = '#' + this.fragment;
}
// Add a cross-platform `addEventListener` shim for older browsers.
@@ -1694,9 +1694,8 @@
// Clean up the iframe if necessary.
if (this.iframe) {
- document.body.removeChild(this.iframeElement);
+ document.body.removeChild(this.iframe);
this.iframe = null;
- this.iframeElement = null;
}
// Some environments will throw when clearing an undefined interval.
@@ -1718,7 +1717,7 @@
// If the user pressed the back button, the iframe's hash will have
// changed and we should use that for comparison.
if (current === this.fragment && this.iframe) {
- current = this.getHash(this.iframe);
+ current = this.getHash(this.iframe.contentWindow);
}
if (current === this.fragment) return false;
@@ -1776,16 +1775,18 @@
// fragment to store history.
} else if (this._wantsHashChange) {
this._updateHash(this.location, fragment, options.replace);
- if (this.iframe && (fragment !== this.getHash(this.iframe))) {
+ if (this.iframe && (fragment !== this.getHash(this.iframe.contentWindow))) {
+ var iWindow = this.iframe.contentWindow;
+
// Opening and closing the iframe tricks IE7 and earlier to push a
// history entry on hash-tag change. When replace is true, we don't
// want this.
if (!options.replace) {
- this.iframe.document.open();
- this.iframe.document.close();
+ iWindow.document.open();
+ iWindow.document.close();
}
- this._updateHash(this.iframe.location, fragment, options.replace);
+ this._updateHash(iWindow.location, fragment, options.replace);
}
// If you've told us that you explicitly don't want fallback hashchange-
From cd11702167608d5e92fc78b4f7931ca89ff0da29 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 12:29:00 -0400
Subject: [PATCH 008/235] Un-golf #changedAttributes
---
backbone.js | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/backbone.js b/backbone.js
index 1dfbd9c5..3b60e6fb 100644
--- a/backbone.js
+++ b/backbone.js
@@ -539,13 +539,14 @@
// determining if there *would be* a change.
changedAttributes: function(diff) {
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
- var val, changed = false;
var old = this._changing ? this._previousAttributes : this.attributes;
+ var changed = {};
for (var attr in diff) {
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
- (changed || (changed = {}))[attr] = val;
+ var val = diff[attr];
+ if (_.isEqual(old[attr], val)) continue;
+ changed[attr] = val;
}
- return changed;
+ return _.size(changed) ? changed : false;
},
// Get the previous value of an attribute, recorded at the time the last
From 632a2b3b67739937cdf7538cb5dfc86e935a8d52 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 12:33:08 -0400
Subject: [PATCH 009/235] Use defaults in #isValid
---
backbone.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index 1dfbd9c5..f8564592 100644
--- a/backbone.js
+++ b/backbone.js
@@ -698,7 +698,7 @@
// Check if the model is currently in a valid state.
isValid: function(options) {
- return this._validate({}, _.extend(options || {}, { validate: true }));
+ return this._validate({}, _.defaults({validate: true}, options));
},
// Run validation against the next complete set of model attributes,
From 962bbe969299a96b18ef6ae7d46b5a534c3c2b69 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 12:36:47 -0400
Subject: [PATCH 010/235] Declare the var when we use it
---
backbone.js | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/backbone.js b/backbone.js
index 1dfbd9c5..d9f8bccb 100644
--- a/backbone.js
+++ b/backbone.js
@@ -581,9 +581,8 @@
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save: function(key, val, options) {
- var attrs, method, xhr, attributes = this.attributes, wait;
-
// Handle both `"key", value` and `{key: value}` -style arguments.
+ var attrs;
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
@@ -592,7 +591,7 @@
}
options = _.extend({validate: true}, options);
- wait = options.wait;
+ var wait = options.wait;
// If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
@@ -613,6 +612,7 @@
if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
+ var attributes = this.attributes;
options.success = function(resp) {
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
@@ -626,9 +626,9 @@
};
wrapError(this, options);
- method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
+ var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch' && !options.attrs) options.attrs = attrs;
- xhr = this.sync(method, this, options);
+ var xhr = this.sync(method, this, options);
// Restore attributes.
if (attrs && wait) this.attributes = attributes;
From 0c2953a0d87ef0b1afce4e2698b5505db8396dc9 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 12:49:55 -0400
Subject: [PATCH 011/235] Respect parse option in Model#fetch
Expand #3508 to cover `Model#fetch`
---
backbone.js | 3 ++-
test/model.js | 15 +++++++++++++++
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index 1dfbd9c5..cee60d29 100644
--- a/backbone.js
+++ b/backbone.js
@@ -569,7 +569,8 @@
var model = this;
var success = options.success;
options.success = function(resp) {
- if (!model.set(model.parse(resp, options), options)) return false;
+ var serverAttrs = options.parse ? model.parse(resp, options) : resp;
+ if (!model.set(serverAttrs, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
};
diff --git a/test/model.js b/test/model.js
index 94647bbd..faaf61dd 100644
--- a/test/model.js
+++ b/test/model.js
@@ -550,6 +550,21 @@
model.destroy(options);
});
+ test("#3470 - save and fetch with parse false", 2, function() {
+ var i = 0;
+ var model = new Backbone.Model();
+ model.parse = function() {
+ ok(false);
+ };
+ model.sync = function(method, model, options) {
+ options.success({i: ++i});
+ };
+ model.fetch({parse: false});
+ equal(model.get('i'), i);
+ model.save(null, {parse: false});
+ equal(model.get('i'), i);
+ });
+
test("save with PATCH", function() {
doc.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4});
doc.save();
From 9407713f19bff95eef783748914a2f5e7d2726b7 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 12:54:50 -0400
Subject: [PATCH 012/235] Default parse during extend
---
backbone.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/backbone.js b/backbone.js
index cee60d29..e3bb67ec 100644
--- a/backbone.js
+++ b/backbone.js
@@ -564,8 +564,7 @@
// Fetch the model from the server, merging the response with the model's
// local attributes. Any changed attributes will trigger a "change" event.
fetch: function(options) {
- options = options ? _.clone(options) : {};
- if (options.parse === void 0) options.parse = true;
+ options = _.extend({parse: true}, options);
var model = this;
var success = options.success;
options.success = function(resp) {
From 5c8c40fe3779e04b3f77e958f002e2d6f0cf3582 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 20:33:13 -0400
Subject: [PATCH 013/235] Test #render returns view
---
test/view.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/test/view.js b/test/view.js
index 392a50f7..a8ec3e67 100644
--- a/test/view.js
+++ b/test/view.js
@@ -48,6 +48,11 @@
strictEqual(new View().one, 1);
});
+ test("render", 1, function() {
+ var view = new Backbone.View;
+ equal(view.render(), view, '#render returns the view instance');
+ });
+
test("delegateEvents", 6, function() {
var counter1 = 0, counter2 = 0;
From fda1f62824ccbbd4b97521322169c6fceadb1376 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 20:33:35 -0400
Subject: [PATCH 014/235] #delegate returns view
---
backbone.js | 1 +
test/view.js | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index 1dfbd9c5..887904b5 100644
--- a/backbone.js
+++ b/backbone.js
@@ -1270,6 +1270,7 @@
// `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
delegate: function(eventName, selector, listener) {
this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
+ return this;
},
// Clears all callbacks previously bound to the view by `delegateEvents`.
diff --git a/test/view.js b/test/view.js
index a8ec3e67..a79315e7 100644
--- a/test/view.js
+++ b/test/view.js
@@ -77,7 +77,7 @@
equal(counter2, 3);
});
- test("delegate", 2, function() {
+ test("delegate", 3, function() {
var view = new Backbone.View({el: '#testElement'});
view.delegate('click', 'h1', function() {
ok(true);
@@ -86,6 +86,8 @@
ok(true);
});
view.$('h1').trigger('click');
+
+ equal(view.delegate(), view, '#delegate returns the view instance');
});
test("delegateEvents allows functions for callbacks", 3, function() {
From dc52dec96c535f5b5492e1f7b7f939e012ddff03 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 20:34:10 -0400
Subject: [PATCH 015/235] #undelegate returns view
---
backbone.js | 1 +
test/view.js | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index 887904b5..d9d5a32b 100644
--- a/backbone.js
+++ b/backbone.js
@@ -1285,6 +1285,7 @@
// `selector` and `listener` are both optional.
undelegate: function(eventName, selector, listener) {
this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
+ return this;
},
// Produces a DOM element to be assigned to your view. Exposed for
diff --git a/test/view.js b/test/view.js
index a79315e7..d78c4c32 100644
--- a/test/view.js
+++ b/test/view.js
@@ -144,7 +144,7 @@
equal(counter2, 3);
});
- test("undelegate", 0, function() {
+ test("undelegate", 1, function() {
view = new Backbone.View({el: '#testElement'});
view.delegate('click', function() { ok(false); });
view.delegate('click', 'h1', function() { ok(false); });
@@ -153,6 +153,8 @@
view.$('h1').trigger('click');
view.$el.trigger('click');
+
+ equal(view.undelegate(), view, '#undelegate returns the view instance');
});
test("undelegate with passed handler", 1, function() {
From dcbd0ea8e6d86d486641fb5753b7a7f9d7f53833 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 20:34:32 -0400
Subject: [PATCH 016/235] Test #undelegateEvents returns view
---
test/view.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/test/view.js b/test/view.js
index d78c4c32..78d79e71 100644
--- a/test/view.js
+++ b/test/view.js
@@ -119,7 +119,7 @@
view.$el.trigger('click');
});
- test("undelegateEvents", 6, function() {
+ test("undelegateEvents", 7, function() {
var counter1 = 0, counter2 = 0;
var view = new Backbone.View({el: '#testElement'});
@@ -142,6 +142,8 @@
view.$('h1').trigger('click');
equal(counter1, 2);
equal(counter2, 3);
+
+ equal(view.undelegateEvents(), view, '#undelegateEvents returns the view instance');
});
test("undelegate", 1, function() {
From 00226d5a5799f3acf9cda2447e15948a24938fe1 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 20:34:55 -0400
Subject: [PATCH 017/235] Test #remove returns view
---
test/view.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/test/view.js b/test/view.js
index 78d79e71..1e068a9e 100644
--- a/test/view.js
+++ b/test/view.js
@@ -398,14 +398,14 @@
equal(counter, 2);
});
- test("remove", 1, function() {
+ test("remove", 2, function() {
var view = new Backbone.View;
document.body.appendChild(view.el);
view.delegate('click', function() { ok(false); });
view.listenTo(view, 'all x', function() { ok(false); });
- view.remove();
+ equal(view.remove(), view, '#remove returns the view instance');
view.$el.trigger('click');
view.trigger('x');
From 11071babd42eeec84af496b402335e8b6941c9e6 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 20:35:03 -0400
Subject: [PATCH 018/235] Test #setElement
---
test/view.js | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/test/view.js b/test/view.js
index 1e068a9e..a3e26974 100644
--- a/test/view.js
+++ b/test/view.js
@@ -413,4 +413,25 @@
notEqual(view.el.parentNode, document.body);
});
+ test("setElement", 3, function() {
+ var view = new Backbone.View({
+ events: {
+ click: function() { ok(false); }
+ }
+ });
+ view.events = {
+ click: function() { ok(true); }
+ };
+ var oldEl = view.el;
+ var $oldEl = view.$el;
+
+ view.setElement(document.createElement('div'));
+
+ $oldEl.click();
+ view.$el.click();
+
+ notEqual(oldEl, view.el);
+ notEqual($oldEl, view.$el);
+ });
+
})();
From 8266bd4609cf3faddf70dde671ced2a3dd434d75 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 20:41:03 -0400
Subject: [PATCH 019/235] Remove unneeded assignment
Since 1.7, `_.pick` no longer throws on `null`
---
backbone.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index 1dfbd9c5..3335901f 100644
--- a/backbone.js
+++ b/backbone.js
@@ -1170,7 +1170,6 @@
// if an existing element is not provided...
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
- options || (options = {});
_.extend(this, _.pick(options, viewOptions));
this._ensureElement();
this.initialize.apply(this, arguments);
From 1f4fb293234b06bc38fabbff11fcf7a2524b61ee Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 20:41:31 -0400
Subject: [PATCH 020/235] Un-golf #delegateEvents
---
backbone.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/backbone.js b/backbone.js
index 3335901f..5764b1cc 100644
--- a/backbone.js
+++ b/backbone.js
@@ -1252,11 +1252,12 @@
// Uses event delegation for efficiency.
// Omitting the selector binds the event to `this.el`.
delegateEvents: function(events) {
- if (!(events || (events = _.result(this, 'events')))) return this;
+ events || (events = _.result(this, 'events'));
+ if (!events) return this;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
- if (!_.isFunction(method)) method = this[events[key]];
+ if (!_.isFunction(method)) method = this[method];
if (!method) continue;
var match = key.match(delegateEventSplitter);
this.delegate(match[1], match[2], _.bind(method, this));
From b5fd59e2fe9abbd15b250b0d0a842370a3d7d87b Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 20:44:43 -0400
Subject: [PATCH 021/235] Grab id using #get in #url
Also, use the same slash normalizing as Router and History
---
backbone.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backbone.js b/backbone.js
index 1dfbd9c5..88b2d19e 100644
--- a/backbone.js
+++ b/backbone.js
@@ -676,8 +676,8 @@
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
- var id = this.id || this.attributes[this.idAttribute];
- return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(id);
+ var id = this.get(this.idAttribute);
+ return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
},
// **parse** converts a response into the hash of attributes to be `set` on
From 4d70b87185db58b3011fd07cee998cfce85e1765 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 12:27:53 -0400
Subject: [PATCH 022/235] Declare variable when it's used
---
backbone.js | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/backbone.js b/backbone.js
index 1dfbd9c5..3e3540e0 100644
--- a/backbone.js
+++ b/backbone.js
@@ -444,10 +444,10 @@
// the core primitive operation of a model, updating the data and notifying
// anyone who needs to know about the change in state. The heart of the beast.
set: function(key, val, options) {
- var attr, attrs, unset, changes, silent, changing, prev, current;
if (key == null) return this;
// Handle both `"key", value` and `{key: value}` -style arguments.
+ var attrs;
if (typeof key === 'object') {
attrs = key;
options = val;
@@ -461,17 +461,20 @@
if (!this._validate(attrs, options)) return false;
// Extract attributes and options.
- unset = options.unset;
- silent = options.silent;
- changes = [];
- changing = this._changing;
- this._changing = true;
+ var unset = options.unset;
+ var silent = options.silent;
+ var changes = [];
+ var changing = this._changing;
+ this._changing = true;
if (!changing) {
this._previousAttributes = _.clone(this.attributes);
this.changed = {};
}
- current = this.attributes, prev = this._previousAttributes;
+
+ var current = this.attributes;
+ var changed = this.changed;
+ var prev = this._previousAttributes;
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
@@ -481,9 +484,9 @@
val = attrs[attr];
if (!_.isEqual(current[attr], val)) changes.push(attr);
if (!_.isEqual(prev[attr], val)) {
- this.changed[attr] = val;
+ changed[attr] = val;
} else {
- delete this.changed[attr];
+ delete changed[attr];
}
unset ? delete current[attr] : current[attr] = val;
}
From 39a596efae141292babce2895be52792b429f341 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 12:37:30 -0400
Subject: [PATCH 023/235] Make it apparent what temporarily needs waited
attributes
---
backbone.js | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/backbone.js b/backbone.js
index d9f8bccb..cf4825e6 100644
--- a/backbone.js
+++ b/backbone.js
@@ -602,11 +602,6 @@
if (!this._validate(attrs, options)) return false;
}
- // Set temporary attributes if `{wait: true}`.
- if (attrs && wait) {
- this.attributes = _.extend({}, attributes, attrs);
- }
-
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
if (options.parse === void 0) options.parse = true;
@@ -626,12 +621,15 @@
};
wrapError(this, options);
+ // Set temporary attributes if `{wait: true}` to properly find new ids.
+ if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
+
var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch' && !options.attrs) options.attrs = attrs;
var xhr = this.sync(method, this, options);
// Restore attributes.
- if (attrs && wait) this.attributes = attributes;
+ this.attributes = attributes;
return xhr;
},
From 0a091770b06fa0c1b6465c33fea9e4eb114e4a35 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 12:38:08 -0400
Subject: [PATCH 024/235] Default parse during extend
---
backbone.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/backbone.js b/backbone.js
index cf4825e6..86390ee2 100644
--- a/backbone.js
+++ b/backbone.js
@@ -590,7 +590,7 @@
(attrs = {})[key] = val;
}
- options = _.extend({validate: true}, options);
+ options = _.extend({validate: true, parse: true}, options);
var wait = options.wait;
// If we're not waiting and attributes exist, save acts as
@@ -604,7 +604,6 @@
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
- if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
var attributes = this.attributes;
From 3601b80b9812ee59704aa0c9b5523f1dfc3700a6 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 12:39:52 -0400
Subject: [PATCH 025/235] Cleanup Model#save success callback
---
backbone.js | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/backbone.js b/backbone.js
index 86390ee2..7bbdaac8 100644
--- a/backbone.js
+++ b/backbone.js
@@ -612,9 +612,7 @@
model.attributes = attributes;
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
if (wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
- if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
- return false;
- }
+ if (serverAttrs && !model.set(serverAttrs, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
};
From dc9b8f152f2fd0aa063972281dfaadf8dafe8d4d Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 23 May 2015 12:40:10 -0400
Subject: [PATCH 026/235] Don't mutate attrs
---
backbone.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index 7bbdaac8..182ddd4a 100644
--- a/backbone.js
+++ b/backbone.js
@@ -611,7 +611,7 @@
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
- if (wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
+ if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
if (serverAttrs && !model.set(serverAttrs, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
From 46c90648c346a4d25560b683bb2d7b6e20271184 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Wed, 27 May 2015 01:00:00 -0400
Subject: [PATCH 027/235] Return removed models from #_removeModels
---
backbone.js | 31 ++++++++++++++-----------------
1 file changed, 14 insertions(+), 17 deletions(-)
diff --git a/backbone.js b/backbone.js
index 1dfbd9c5..d2201b70 100644
--- a/backbone.js
+++ b/backbone.js
@@ -776,12 +776,12 @@
// Remove a model, or a list of models from the set.
remove: function(models, options) {
- var singular = !_.isArray(models), removed;
+ options = _.extend({}, options);
+ var singular = !_.isArray(models);
models = singular ? [models] : _.clone(models);
- options || (options = {});
- removed = this._removeModels(models, options);
+ var removed = this._removeModels(models, options);
if (!options.silent && removed) this.trigger('update', this, options);
- return singular ? models[0] : models;
+ return singular ? removed[0] : removed;
},
// Update a collection by `set`-ing a new list of models, adding new ones,
@@ -1060,31 +1060,28 @@
return false;
},
- // Internal method called by both remove and set. Does not trigger any
- // additional events. Returns true if anything was actually removed.
+ // Internal method called by both remove and set.
+ // Returns removed models, or false if nothing is removed.
_removeModels: function(models, options) {
- var i, l, index, model, removed = false;
- for (var i = 0, j = 0; i < models.length; i++) {
+ options = _.extend({}, options);
+ var removed = [];
+ for (var i = 0; i < models.length; i++) {
var model = models[i] = this.get(models[i]);
if (!model) continue;
- var id = this.modelId(model.attributes);
- if (id != null) delete this._byId[id];
- delete this._byId[model.cid];
+
var index = this.indexOf(model);
this.models.splice(index, 1);
this.length--;
+
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
- models[j++] = model;
+
+ removed.push(model);
this._removeReference(model, options);
- removed = true;
}
- // We only need to slice if models array should be smaller, which is
- // caused by some models not actually getting removed.
- if (models.length !== j) models = models.slice(0, j);
- return removed;
+ return removed.length ? removed : false;
},
// Method for checking whether an object should be considered a model for
From a910bace0999c8c360c2f60e2e7226c02814ca4b Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Wed, 27 May 2015 01:00:45 -0400
Subject: [PATCH 028/235] Collection#remove returns removed model
---
backbone.js | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/backbone.js b/backbone.js
index d2201b70..a5949e51 100644
--- a/backbone.js
+++ b/backbone.js
@@ -761,7 +761,7 @@
// The JSON representation of a Collection is an array of the
// models' attributes.
toJSON: function(options) {
- return this.map(function(model){ return model.toJSON(options); });
+ return this.map(function(model) { return model.toJSON(options); });
},
// Proxy `Backbone.sync` by default.
@@ -910,8 +910,7 @@
// Remove a model from the end of the collection.
pop: function(options) {
var model = this.at(this.length - 1);
- this.remove(model, options);
- return model;
+ return this.remove(model, options);
},
// Add a model to the beginning of the collection.
@@ -922,8 +921,7 @@
// Remove a model from the beginning of the collection.
shift: function(options) {
var model = this.at(0);
- this.remove(model, options);
- return model;
+ return this.remove(model, options);
},
// Slice out a sub-array of models from the collection.
From 61e765087d955266225dc2eefad26122f1af9975 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Wed, 27 May 2015 11:42:42 -0400
Subject: [PATCH 029/235] Test that Collection#remove _only_ returns removed
models
Add (currently) failing tests for #3512.
---
test/collection.js | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/test/collection.js b/test/collection.js
index 0ac4b756..49c36c68 100644
--- a/test/collection.js
+++ b/test/collection.js
@@ -298,17 +298,13 @@
deepEqual(col.pluck('id'), [1, 2, 3]);
});
- test("remove", 7, function() {
+ test("remove", 10, function() {
var removed = null;
- var otherRemoved = null;
var result = null;
col.on('remove', function(model, col, options) {
removed = model.get('label');
equal(options.index, 3);
});
- otherCol.on('remove', function(model, col, options) {
- otherRemoved = true;
- });
result = col.remove(d);
equal(removed, 'd');
strictEqual(result, d);
@@ -317,7 +313,13 @@
strictEqual(result, undefined);
equal(col.length, 3);
equal(col.first(), a);
- equal(otherRemoved, null);
+ col.off();
+ result = col.remove([c, d]);
+ equal(result.length, 1, 'only returns removed models');
+ equal(result[0], c, 'only returns removed models');
+ result = col.remove([c, b]);
+ equal(result.length, 1, 'only returns removed models');
+ equal(result[0], b, 'only returns removed models');
});
test("add and remove return values", 13, function() {
From e926394658dc5871e24e56e13d15194ca098a85a Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Wed, 27 May 2015 11:44:27 -0400
Subject: [PATCH 030/235] #_removeReference now removes references
---
backbone.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/backbone.js b/backbone.js
index a5949e51..e778704f 100644
--- a/backbone.js
+++ b/backbone.js
@@ -1098,6 +1098,9 @@
// Internal method to sever a model's ties to a collection.
_removeReference: function(model, options) {
+ delete this._byId[model.cid];
+ var id = this.modelId(model.attributes);
+ if (id != null) delete this._byId[id] = model;
if (this === model.collection) delete model.collection;
model.off('all', this._onModelEvent, this);
},
From 196cee72892848479e7a856ea80fd0a809458918 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Wed, 27 May 2015 11:53:37 -0400
Subject: [PATCH 031/235] Fix syntax error.
---
backbone.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index e778704f..883fae42 100644
--- a/backbone.js
+++ b/backbone.js
@@ -1100,7 +1100,7 @@
_removeReference: function(model, options) {
delete this._byId[model.cid];
var id = this.modelId(model.attributes);
- if (id != null) delete this._byId[id] = model;
+ if (id != null) delete this._byId[id];
if (this === model.collection) delete model.collection;
model.off('all', this._onModelEvent, this);
},
From cc2581598dba49c35ac360cfe0f297853e36adc5 Mon Sep 17 00:00:00 2001
From: Adam Krebs
Date: Wed, 27 May 2015 12:14:00 -0400
Subject: [PATCH 032/235] correct "polymorphic" typo
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index 30a61b5c..ad014f5d 100644
--- a/index.html
+++ b/index.html
@@ -1780,7 +1780,7 @@ var Library = Backbone.Collection.extend({
refer to its models in collection.get.
By default returns the idAttribute of the collection's model
- class or failing that, 'id'. If your collection uses pollymorphic
+ class or failing that, 'id'. If your collection uses polymorphic
models and those models have an idAttribute other than id
you must override this method with your own custom logic.
From 875af33298bb41e968453ae20fe5d7034f1ff4ce Mon Sep 17 00:00:00 2001
From: Adam Krebs
Date: Wed, 27 May 2015 12:16:00 -0400
Subject: [PATCH 033/235] merge in master docs
---
index.html | 57 +++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 44 insertions(+), 13 deletions(-)
diff --git a/index.html b/index.html
index 9240cdf8..ad014f5d 100644
--- a/index.html
+++ b/index.html
@@ -377,6 +377,7 @@
- - "add" (model, collection, options) — when a model is added to a collection.
- - "remove" (model, collection, options) — when a model is removed from a collection.
- - "update" (collection, options) — single event triggered after any number of models have been added or removed from a collection.
- - "reset" (collection, options) — when the collection's entire contents have been replaced.
- - "sort" (collection, options) — when the collection has been re-sorted.
- - "change" (model, options) — when a model's attributes have changed.
- - "change:[attribute]" (model, value, options) — when a specific attribute has been updated.
- - "destroy" (model, collection, options) — when a model is destroyed.
- - "request" (model_or_collection, xhr, options) — when a model or collection has started a request to the server.
+ - "add" (model, collection, options) — when a model is added to a collection.
+ - "remove" (model, collection, options) — when a model is removed from a collection.
+ - "update" (collection, options) — single event triggered after any number of models have been added or removed from a collection.
+ - "reset" (collection, options) — when the collection's entire contents have been replaced.
+ - "sort" (collection, options) — when the collection has been re-sorted.
+ - "change" (model, options) — when a model's attributes have changed.
+ - "change:[attribute]" (model, value, options) — when a specific attribute has been updated.
+ - "destroy" (model, collection, options) — when a model is destroyed.
+ - "request" (model_or_collection, xhr, options) — when a model or collection has started a request to the server.
- "sync" (model_or_collection, resp, options) — when a model or collection has been successfully synced with the server.
- - "error" (model_or_collection, resp, options) — when a model's or collection's request to the server has failed.
- - "invalid" (model, error, options) — when a model's validation fails on the client.
+ - "error" (model_or_collection, resp, options) — when a model's or collection's request to the server has failed.
+ - "invalid" (model, error, options) — when a model's validation fails on the client.
- "route:[name]" (params) — Fired by the router when a specific route is matched.
- "route" (route, params) — Fired by the router when any route has been matched.
- "route" (router, route, params) — Fired by history when any route has been matched.
- - "all" — this special event fires for any triggered event, passing the event name as the first argument.
+ - "all" — this special event fires for any triggered event, passing the event name as the first argument.
@@ -1769,6 +1770,36 @@ var Library = Backbone.Collection.extend({
}
}
+});
+
+
+
+ collection.modelId
+
+ Override this method to specify the attribute the collection will use to
+ refer to its models in collection.get.
+
+ By default returns the idAttribute of the collection's model
+ class or failing that, 'id'. If your collection uses polymorphic
+ models and those models have an idAttribute other than id
+ you must override this method with your own custom logic.
+
+
+
+var Library = Backbone.Collection.extend({
+
+ model: function(attrs, options) {
+ if (condition) {
+ return new PublicDocument(attrs, options);
+ } else {
+ return new PrivateDocument(attrs, options);
+ }
+ },
+
+ modelId: function(attrs) {
+ return attrs.private ? 'private_id' : 'public_id';
+ }
+
});
@@ -1894,7 +1925,7 @@ var alphabetical = books.sortBy(function(book) {
collection.add(models, [options])
Add a model (or an array of models) to the collection, firing an "add"
- event for each model, and an "updated" event afterwards. If a model property is defined, you may also pass
+ event for each model, and an "update" event afterwards. If a model property is defined, you may also pass
raw attributes objects, and have them be vivified as instances of the model.
Returns the added (or preexisting, if duplicate) models.
Pass {at: index} to splice the model into the collection at the
From 15215706f1dc1a2bf8b1c7cf78581ba5dc102de6 Mon Sep 17 00:00:00 2001
From: Adam Krebs
Date: Wed, 27 May 2015 14:36:54 -0400
Subject: [PATCH 034/235] Document delegateEvents change from #3060
---
index.html | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/index.html b/index.html
index ad014f5d..70205904 100644
--- a/index.html
+++ b/index.html
@@ -4256,6 +4256,11 @@ ActiveRecord::Base.include_root_in_json = false
Also on the subject of jQuery, if you're using Backbone with CommonJS (node, browserify, webpack)
Backbone will automatically try to load jQuery for you.
+
+ Views now always delegate their events in setElement.
+ You can no longer modify the elements hash or your view's el property in
+ initialize.
+
Added an "update" event that triggers after any amount of
models are added or removed from a collection. Handy to re-render lists
From ff63c791cc2892260755bbe2ae840419f9284c2e Mon Sep 17 00:00:00 2001
From: Adam Krebs
Date: Wed, 27 May 2015 14:36:54 -0400
Subject: [PATCH 035/235] Document delegateEvents change from #3060
---
index.html | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/index.html b/index.html
index ad014f5d..70205904 100644
--- a/index.html
+++ b/index.html
@@ -4256,6 +4256,11 @@ ActiveRecord::Base.include_root_in_json = false
Also on the subject of jQuery, if you're using Backbone with CommonJS (node, browserify, webpack)
Backbone will automatically try to load jQuery for you.
+
+ Views now always delegate their events in setElement.
+ You can no longer modify the elements hash or your view's el property in
+ initialize.
+
Added an "update" event that triggers after any amount of
models are added or removed from a collection. Handy to re-render lists
From 2195406c4b3e48affbc4ce8cd97bd9ac86407dde Mon Sep 17 00:00:00 2001
From: Adam Krebs
Date: Wed, 27 May 2015 22:34:44 -0400
Subject: [PATCH 036/235] typo: elements -> events
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index 70205904..32cbb9e7 100644
--- a/index.html
+++ b/index.html
@@ -4258,7 +4258,7 @@ ActiveRecord::Base.include_root_in_json = false
Views now always delegate their events in setElement.
- You can no longer modify the elements hash or your view's el property in
+ You can no longer modify the events hash or your view's el property in
initialize.
From 72d9c00fb4126d3f83d7056db8e479b876a435e5 Mon Sep 17 00:00:00 2001
From: Adam Krebs
Date: Wed, 27 May 2015 22:34:44 -0400
Subject: [PATCH 037/235] typo: elements -> events
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index 70205904..32cbb9e7 100644
--- a/index.html
+++ b/index.html
@@ -4258,7 +4258,7 @@ ActiveRecord::Base.include_root_in_json = false
Views now always delegate their events in setElement.
- You can no longer modify the elements hash or your view's el property in
+ You can no longer modify the events hash or your view's el property in
initialize.
From bec0e4a1a910ff45272d74a892c8c2d5bd40a23d Mon Sep 17 00:00:00 2001
From: Alexandru Bucur
Date: Fri, 29 May 2015 12:07:26 +0300
Subject: [PATCH 038/235] updated TodoMVC link
---
examples/todos/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/todos/index.html b/examples/todos/index.html
index 6f778783..e737fd2c 100644
--- a/examples/todos/index.html
+++ b/examples/todos/index.html
@@ -37,7 +37,7 @@
Created by
Jérôme Gravel-Niquet.
-
Rewritten by: TodoMVC.
+
Rewritten by: TodoMVC.
From 415c3a7a133e551675e8e23893750c54c97aae61 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Fri, 29 May 2015 20:47:48 -0400
Subject: [PATCH 039/235] Collection#set shouldn't parse
Fixes #3636, #2824.
Reverts #1407.
---
backbone.js | 10 ++---
test/collection.js | 99 +++++++++++++++++++++-------------------------
2 files changed, 49 insertions(+), 60 deletions(-)
diff --git a/backbone.js b/backbone.js
index d677f071..145175df 100644
--- a/backbone.js
+++ b/backbone.js
@@ -739,6 +739,7 @@
if (options.comparator !== void 0) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
+ if (models && options.parse) models = this.parse(models, options);
if (models) this.reset(models, _.extend({silent: true}, options));
};
@@ -789,7 +790,6 @@
// the core operation for updating the data contained by the collection.
set: function(models, options) {
options = _.defaults({}, options, setOptions);
- if (options.parse) models = this.parse(models, options);
var singular = !_.isArray(models);
models = singular ? (models ? [models] : []) : models.slice();
var id, model, attrs, existing, sort;
@@ -983,13 +983,13 @@
// collection when they arrive. If `reset: true` is passed, the response
// data will be passed through the `reset` method instead of `set`.
fetch: function(options) {
- options = options ? _.clone(options) : {};
- if (options.parse === void 0) options.parse = true;
- var success = options.success;
+ options = _.extend({parse: true}, options);
var collection = this;
+ var success = options.success;
options.success = function(resp) {
var method = options.reset ? 'reset' : 'set';
- collection[method](resp, options);
+ var models = options.parse ? collection.parse(resp, options) : resp;
+ collection[method](models, options);
if (success) success.call(options.context, collection, resp, options);
collection.trigger('sync', collection, resp, options);
};
diff --git a/test/collection.js b/test/collection.js
index 49c36c68..9b46aa7b 100644
--- a/test/collection.js
+++ b/test/collection.js
@@ -252,19 +252,6 @@
equal(col.at(0).get('value'), 2);
});
- test("add with parse and merge", function() {
- var collection = new Backbone.Collection();
- collection.parse = function(attrs) {
- return _.map(attrs, function(model) {
- if (model.model) return model.model;
- return model;
- });
- };
- collection.add({id: 1});
- collection.add({model: {id: 1, name: 'Alf'}}, {parse: true, merge: true});
- equal(collection.first().get('name'), 'Alf');
- });
-
test("add model to collection with sort()-style comparator", 3, function() {
var col = new Backbone.Collection;
col.comparator = function(a, b) {
@@ -978,29 +965,6 @@
equal(c.at(0).get('name'), 'test');
});
- test("#1407 parse option on reset parses collection and models", 2, function() {
- var model = {
- namespace : [{id: 1}, {id:2}]
- };
- var Collection = Backbone.Collection.extend({
- model: Backbone.Model.extend({
- parse: function(model) {
- model.name = 'test';
- return model;
- }
- }),
- parse: function(model) {
- return model.namespace;
- }
- });
- var c = new Collection();
- c.reset(model, {parse:true});
-
- equal(c.length, 2);
- equal(c.at(0).get('name'), 'test');
- });
-
-
test("Reset includes previous models in triggered event.", 1, function() {
var model = new Backbone.Model();
var collection = new Backbone.Collection([model])
@@ -1123,18 +1087,21 @@
});
test("`set` and model level `parse`", function() {
- var Model = Backbone.Model.extend({});
+ var Model = Backbone.Model.extend({
+ parse: function(model) {
+ return model.model;
+ }
+ });
var Collection = Backbone.Collection.extend({
- model: Model,
- parse: function (res) { return _.pluck(res.models, 'model'); }
+ model: Model
});
var model = new Model({id: 1});
var collection = new Collection(model);
- collection.set({models: [
- {model: {id: 1}},
- {model: {id: 2}}
- ]}, {parse: true});
- equal(collection.first(), model);
+ collection.set([
+ {model: {id: 1, attr: 'test'}},
+ {model: {id: 2, attr: 'test'}}
+ ], {parse: true});
+ deepEqual(collection.pluck('attr'), ['test', 'test']);
});
test("`set` data is only parsed once", function() {
@@ -1200,17 +1167,6 @@
new Collection().add({id: 1}, {sort: false});
});
- test("#1915 - `parse` data in the right order in `set`", function() {
- var collection = new (Backbone.Collection.extend({
- parse: function (data) {
- strictEqual(data.status, 'ok');
- return data.data;
- }
- }));
- var res = {status: 'ok', data:[{id: 1}]};
- collection.set(res, {parse: true});
- });
-
asyncTest("#1939 - `parse` is passed `options`", 1, function () {
var collection = new (Backbone.Collection.extend({
url: '/',
@@ -1622,4 +1578,37 @@
collection.invoke('method', 1, 2, 3);
});
+ test("set, add, and reset do not parse at collection level", 0, function() {
+ var Collection = Backbone.Collection.extend({
+ parse: function(models) {
+ ok(false);
+ }
+ });
+ var c = new Collection();
+ c.set({id: 1}, {parse: true});
+ c.add([{id: 2}, {id: 3}], {parse: true});
+ c.reset([{id: 4}, {id: 5}], {parse: true});
+ });
+
+ test("#3636 - create does not parse at collection level", 4, function() {
+ var Collection = Backbone.Collection.extend({
+ url: '/test',
+ model: Backbone.Model.extend({
+ parse: function(model) {
+ ok(true);
+ return model.model;
+ }
+ }),
+ parse: function(models) {
+ ok(false);
+ }
+ });
+ var c = new Collection();
+ c.create({}, {parse: true});
+ this.ajaxSettings.success({model: {id: 1}});
+ c.create(c.first(), {parse: true});
+ this.ajaxSettings.success({model: {attr: 'test'}});
+ equal(c.get(1).get('attr'), 'test');
+ });
+
})();
From 13de636df1862fb331735e5d019e9ba8c01430d0 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Fri, 29 May 2015 21:11:45 -0400
Subject: [PATCH 040/235] Ensure fetch'd data is only parsed once
---
test/collection.js | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/test/collection.js b/test/collection.js
index 9b46aa7b..2cee05d4 100644
--- a/test/collection.js
+++ b/test/collection.js
@@ -1611,4 +1611,28 @@
equal(c.get(1).get('attr'), 'test');
});
+ test("fetch data is only parsed once", 3, function() {
+ var modelParse = 0;
+ var collectionParse = 0;
+ var Collection = Backbone.Collection.extend({
+ url: '/test',
+ model: Backbone.Model.extend({
+ parse: function(model) {
+ modelParse++;
+ return model.model;
+ }
+ }),
+ parse: function(models) {
+ collectionParse++;
+ return models.models;
+ }
+ });
+ var c = new Collection();
+ c.fetch();
+ this.ajaxSettings.success({models: [{model: {id: 1, attr: 'test'} }] });
+ equal(modelParse, 1);
+ equal(collectionParse, 1);
+ equal(c.get(1).get('attr'), 'test');
+ });
+
})();
From 1a8ed2efc6fc8d27b1de11ed097297bdda67e835 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sat, 30 May 2015 23:13:02 -0400
Subject: [PATCH 041/235] Variable cleanup
---
backbone.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backbone.js b/backbone.js
index d677f071..afd37537 100644
--- a/backbone.js
+++ b/backbone.js
@@ -205,7 +205,7 @@
// No events to consider.
if (!events) return;
- var i = 0, length, listening;
+ var i = 0, listening;
var context = options.context, listeners = options.listeners;
// Delete all events listeners and "drop" events.
@@ -480,7 +480,7 @@
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
// For each `set` attribute, update or delete the current value.
- for (attr in attrs) {
+ for (var attr in attrs) {
val = attrs[attr];
if (!_.isEqual(current[attr], val)) changes.push(attr);
if (!_.isEqual(prev[attr], val)) {
From be7fc959641a3ccaa362110023ea7d697bc3f93b Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 00:34:12 -0400
Subject: [PATCH 042/235] Update model's id with #get
---
backbone.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/backbone.js b/backbone.js
index afd37537..c150558d 100644
--- a/backbone.js
+++ b/backbone.js
@@ -476,9 +476,6 @@
var changed = this.changed;
var prev = this._previousAttributes;
- // Check for changes of `id`.
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
-
// For each `set` attribute, update or delete the current value.
for (var attr in attrs) {
val = attrs[attr];
@@ -491,6 +488,9 @@
unset ? delete current[attr] : current[attr] = val;
}
+ // Update the `id`.
+ this.id = this.get(this.idAttribute);
+
// Trigger all relevant attribute changes.
if (!silent) {
if (changes.length) this._pending = options;
From 53a4e701bb9717809cca71e01566594b4eef584f Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 01:54:37 -0400
Subject: [PATCH 043/235] Remove unnecessary assignment
---
backbone.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index afd37537..778507a1 100644
--- a/backbone.js
+++ b/backbone.js
@@ -1063,7 +1063,7 @@
options = _.extend({}, options);
var removed = [];
for (var i = 0; i < models.length; i++) {
- var model = models[i] = this.get(models[i]);
+ var model = this.get(models[i]);
if (!model) continue;
var index = this.indexOf(model);
From 565b2a1952ac82fd94505d6de57dc1ab070f452a Mon Sep 17 00:00:00 2001
From: Adam Krebs
Date: Mon, 1 Jun 2015 11:36:43 -0400
Subject: [PATCH 044/235] Move addUnderscoreMethods out of Backbone.Events,
adjust comment now that array methods is only 1 method
---
backbone.js | 63 ++++++++++++++++++++++++++---------------------------
1 file changed, 31 insertions(+), 32 deletions(-)
diff --git a/backbone.js b/backbone.js
index d677f071..6fe68c1a 100644
--- a/backbone.js
+++ b/backbone.js
@@ -40,9 +40,8 @@
// restored later on, if `noConflict` is used.
var previousBackbone = root.Backbone;
- // Create local references to array methods we'll want to use later.
- var array = [];
- var slice = array.slice;
+ // Create a local reference to a common array method we'll want to use later.
+ var slice = [].slice;
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '1.2.0';
@@ -69,6 +68,35 @@
// form param named `model`.
Backbone.emulateJSON = false;
+ // Proxy Underscore methods to a Backbone class' prototype using a
+ // particular attribute as the data argument
+ var addMethod = function(length, method, attribute) {
+ switch (length) {
+ case 1: return function() {
+ return _[method](this[attribute]);
+ };
+ case 2: return function(value) {
+ return _[method](this[attribute], value);
+ };
+ case 3: return function(iteratee, context) {
+ return _[method](this[attribute], iteratee, context);
+ };
+ case 4: return function(iteratee, defaultVal, context) {
+ return _[method](this[attribute], iteratee, defaultVal, context);
+ };
+ default: return function() {
+ var args = slice.call(arguments);
+ args.unshift(this[attribute]);
+ return _[method].apply(_, args);
+ };
+ }
+ };
+ var addUnderscoreMethods = function(Class, methods, attribute) {
+ _.each(methods, function(length, method) {
+ if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
+ });
+ };
+
// Backbone.Events
// ---------------
@@ -327,35 +355,6 @@
}
};
- // Proxy Underscore methods to a Backbone class' prototype using a
- // particular attribute as the data argument
- var addMethod = function(length, method, attribute) {
- switch (length) {
- case 1: return function() {
- return _[method](this[attribute]);
- };
- case 2: return function(value) {
- return _[method](this[attribute], value);
- };
- case 3: return function(iteratee, context) {
- return _[method](this[attribute], iteratee, context);
- };
- case 4: return function(iteratee, defaultVal, context) {
- return _[method](this[attribute], iteratee, defaultVal, context);
- };
- default: return function() {
- var args = slice.call(arguments);
- args.unshift(this[attribute]);
- return _[method].apply(_, args);
- };
- }
- };
- var addUnderscoreMethods = function(Class, methods, attribute) {
- _.each(methods, function(length, method) {
- if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
- });
- };
-
// Aliases for backwards compatibility.
Events.bind = Events.on;
Events.unbind = Events.off;
From 9be4131258a3c1e2c925bc60989181ecc2adb147 Mon Sep 17 00:00:00 2001
From: Adam Krebs
Date: Mon, 1 Jun 2015 11:53:03 -0400
Subject: [PATCH 045/235] fix typo in once comment
---
backbone.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index ff7e1369..b787bda3 100644
--- a/backbone.js
+++ b/backbone.js
@@ -275,7 +275,7 @@
};
// Reduces the event callbacks into a map of `{event: onceWrapper}`.
- // `offer` unbinds the `onceWrapper` after it as been called.
+ // `offer` unbinds the `onceWrapper` after it has been called.
var onceMap = function(map, name, callback, offer) {
if (callback) {
var once = map[name] = _.once(function() {
From 591ab696db26bf0b6fa745afc1712e4fbf1cf6ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Roucheray?=
Date: Mon, 1 Jun 2015 21:55:15 +0200
Subject: [PATCH 046/235] Delete `main` property duplication
---
package.json | 1 -
1 file changed, 1 deletion(-)
diff --git a/package.json b/package.json
index e75c19f1..f3dcbcc5 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,6 @@
"karma-qunit": "^0.1.4",
"uglify-js": "^2.4.17"
},
- "main": "backbone.js",
"scripts": {
"test": "./node_modules/karma/bin/karma start && coffee test/model.coffee",
"build": "uglifyjs backbone.js --mangle --source-map backbone-min.map -o backbone-min.js",
From f79e0bcf5327238f5baabf606260ee610c61f02a Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Tue, 2 Jun 2015 12:26:41 -0400
Subject: [PATCH 047/235] Fix failing IE6 test
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
It was a false positive. Our “location” parsing uses an A tag, which
doesn’t always work the way the real location parsing does.
---
test/router.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/router.js b/test/router.js
index db2db909..acd17cec 100644
--- a/test/router.js
+++ b/test/router.js
@@ -918,7 +918,7 @@
test('#3358 - pushState to hashChange transition with search params', 1, function() {
Backbone.history.stop();
- location.replace('/root?foo=bar');
+ location.replace('http://example.com/root?foo=bar');
location.replace = function(url) {
strictEqual(url, '/root#?foo=bar');
};
From c95d9775e76d7750124368d0d4ba84e348c19c78 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Tue, 2 Jun 2015 13:55:21 -0400
Subject: [PATCH 048/235] Revert "Merge pull request #3644 from
jridgewell/collection-parse"
This reverts commit ff57b54cbb1b1c0d297d25bf673c8adc7023dd69, reversing
changes made to 5ea158506ff65def13b132b30f0b0c9ecb0d31a2.
---
backbone.js | 10 ++--
test/collection.js | 123 ++++++++++++++++++++-------------------------
2 files changed, 60 insertions(+), 73 deletions(-)
diff --git a/backbone.js b/backbone.js
index 35dd3086..53fb0872 100644
--- a/backbone.js
+++ b/backbone.js
@@ -739,7 +739,6 @@
if (options.comparator !== void 0) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
- if (models && options.parse) models = this.parse(models, options);
if (models) this.reset(models, _.extend({silent: true}, options));
};
@@ -790,6 +789,7 @@
// the core operation for updating the data contained by the collection.
set: function(models, options) {
options = _.defaults({}, options, setOptions);
+ if (options.parse) models = this.parse(models, options);
var singular = !_.isArray(models);
models = singular ? (models ? [models] : []) : models.slice();
var id, model, attrs, existing, sort;
@@ -983,13 +983,13 @@
// collection when they arrive. If `reset: true` is passed, the response
// data will be passed through the `reset` method instead of `set`.
fetch: function(options) {
- options = _.extend({parse: true}, options);
- var collection = this;
+ options = options ? _.clone(options) : {};
+ if (options.parse === void 0) options.parse = true;
var success = options.success;
+ var collection = this;
options.success = function(resp) {
var method = options.reset ? 'reset' : 'set';
- var models = options.parse ? collection.parse(resp, options) : resp;
- collection[method](models, options);
+ collection[method](resp, options);
if (success) success.call(options.context, collection, resp, options);
collection.trigger('sync', collection, resp, options);
};
diff --git a/test/collection.js b/test/collection.js
index 2cee05d4..49c36c68 100644
--- a/test/collection.js
+++ b/test/collection.js
@@ -252,6 +252,19 @@
equal(col.at(0).get('value'), 2);
});
+ test("add with parse and merge", function() {
+ var collection = new Backbone.Collection();
+ collection.parse = function(attrs) {
+ return _.map(attrs, function(model) {
+ if (model.model) return model.model;
+ return model;
+ });
+ };
+ collection.add({id: 1});
+ collection.add({model: {id: 1, name: 'Alf'}}, {parse: true, merge: true});
+ equal(collection.first().get('name'), 'Alf');
+ });
+
test("add model to collection with sort()-style comparator", 3, function() {
var col = new Backbone.Collection;
col.comparator = function(a, b) {
@@ -965,6 +978,29 @@
equal(c.at(0).get('name'), 'test');
});
+ test("#1407 parse option on reset parses collection and models", 2, function() {
+ var model = {
+ namespace : [{id: 1}, {id:2}]
+ };
+ var Collection = Backbone.Collection.extend({
+ model: Backbone.Model.extend({
+ parse: function(model) {
+ model.name = 'test';
+ return model;
+ }
+ }),
+ parse: function(model) {
+ return model.namespace;
+ }
+ });
+ var c = new Collection();
+ c.reset(model, {parse:true});
+
+ equal(c.length, 2);
+ equal(c.at(0).get('name'), 'test');
+ });
+
+
test("Reset includes previous models in triggered event.", 1, function() {
var model = new Backbone.Model();
var collection = new Backbone.Collection([model])
@@ -1087,21 +1123,18 @@
});
test("`set` and model level `parse`", function() {
- var Model = Backbone.Model.extend({
- parse: function(model) {
- return model.model;
- }
- });
+ var Model = Backbone.Model.extend({});
var Collection = Backbone.Collection.extend({
- model: Model
+ model: Model,
+ parse: function (res) { return _.pluck(res.models, 'model'); }
});
var model = new Model({id: 1});
var collection = new Collection(model);
- collection.set([
- {model: {id: 1, attr: 'test'}},
- {model: {id: 2, attr: 'test'}}
- ], {parse: true});
- deepEqual(collection.pluck('attr'), ['test', 'test']);
+ collection.set({models: [
+ {model: {id: 1}},
+ {model: {id: 2}}
+ ]}, {parse: true});
+ equal(collection.first(), model);
});
test("`set` data is only parsed once", function() {
@@ -1167,6 +1200,17 @@
new Collection().add({id: 1}, {sort: false});
});
+ test("#1915 - `parse` data in the right order in `set`", function() {
+ var collection = new (Backbone.Collection.extend({
+ parse: function (data) {
+ strictEqual(data.status, 'ok');
+ return data.data;
+ }
+ }));
+ var res = {status: 'ok', data:[{id: 1}]};
+ collection.set(res, {parse: true});
+ });
+
asyncTest("#1939 - `parse` is passed `options`", 1, function () {
var collection = new (Backbone.Collection.extend({
url: '/',
@@ -1578,61 +1622,4 @@
collection.invoke('method', 1, 2, 3);
});
- test("set, add, and reset do not parse at collection level", 0, function() {
- var Collection = Backbone.Collection.extend({
- parse: function(models) {
- ok(false);
- }
- });
- var c = new Collection();
- c.set({id: 1}, {parse: true});
- c.add([{id: 2}, {id: 3}], {parse: true});
- c.reset([{id: 4}, {id: 5}], {parse: true});
- });
-
- test("#3636 - create does not parse at collection level", 4, function() {
- var Collection = Backbone.Collection.extend({
- url: '/test',
- model: Backbone.Model.extend({
- parse: function(model) {
- ok(true);
- return model.model;
- }
- }),
- parse: function(models) {
- ok(false);
- }
- });
- var c = new Collection();
- c.create({}, {parse: true});
- this.ajaxSettings.success({model: {id: 1}});
- c.create(c.first(), {parse: true});
- this.ajaxSettings.success({model: {attr: 'test'}});
- equal(c.get(1).get('attr'), 'test');
- });
-
- test("fetch data is only parsed once", 3, function() {
- var modelParse = 0;
- var collectionParse = 0;
- var Collection = Backbone.Collection.extend({
- url: '/test',
- model: Backbone.Model.extend({
- parse: function(model) {
- modelParse++;
- return model.model;
- }
- }),
- parse: function(models) {
- collectionParse++;
- return models.models;
- }
- });
- var c = new Collection();
- c.fetch();
- this.ajaxSettings.success({models: [{model: {id: 1, attr: 'test'} }] });
- equal(modelParse, 1);
- equal(collectionParse, 1);
- equal(c.get(1).get('attr'), 'test');
- });
-
})();
From ffa4fa597de154d704161a5df584e0458fa5f1a5 Mon Sep 17 00:00:00 2001
From: Andrey Kuzmin
Date: Tue, 26 May 2015 22:58:02 +0200
Subject: [PATCH 049/235] Added failing test case
---
test/collection.js | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/test/collection.js b/test/collection.js
index 49c36c68..507191e3 100644
--- a/test/collection.js
+++ b/test/collection.js
@@ -561,6 +561,34 @@
});
+ test("create with wait:true should not call collection.parse", 1, function() {
+ var collectionParseCalled = false;
+
+ var Model = Backbone.Model.extend({
+ sync: function (method, model, options) {
+ _.extend(options, {specialSync: true});
+ return Backbone.Model.prototype.sync.call(this, method, model, options);
+ }
+ });
+
+ var Collection = Backbone.Collection.extend({
+ model: Model,
+ url: '/test',
+ parse: function () {
+ collectionParseCalled = true;
+ }
+ });
+
+ var collection = new Collection;
+
+ var success = function (model, response, options) {
+ equal(collectionParseCalled, false);
+ };
+
+ collection.create({}, {wait: true, success: success});
+ this.ajaxSettings.success();
+ });
+
test("a failing create returns model with errors", function() {
var ValidatingModel = Backbone.Model.extend({
validate: function(attrs) {
From fdea29dfc812af03da22c13702f418b7c4d3af13 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Tue, 2 Jun 2015 14:06:50 -0400
Subject: [PATCH 050/235] Collection#add shouldn't parse a model instance
Fixes #3636, but in a backwards compatible way.
---
backbone.js | 6 +++---
test/collection.js | 20 +++-----------------
2 files changed, 6 insertions(+), 20 deletions(-)
diff --git a/backbone.js b/backbone.js
index 53fb0872..945a5458 100644
--- a/backbone.js
+++ b/backbone.js
@@ -789,7 +789,7 @@
// the core operation for updating the data contained by the collection.
set: function(models, options) {
options = _.defaults({}, options, setOptions);
- if (options.parse) models = this.parse(models, options);
+ if (options.parse && !this._isModel(models)) models = this.parse(models, options);
var singular = !_.isArray(models);
models = singular ? (models ? [models] : []) : models.slice();
var id, model, attrs, existing, sort;
@@ -1776,7 +1776,7 @@
this._updateHash(this.location, fragment, options.replace);
if (this.iframe && (fragment !== this.getHash(this.iframe.contentWindow))) {
var iWindow = this.iframe.contentWindow;
-
+
// Opening and closing the iframe tricks IE7 and earlier to push a
// history entry on hash-tag change. When replace is true, we don't
// want this.
@@ -1784,7 +1784,7 @@
iWindow.document.open();
iWindow.document.close();
}
-
+
this._updateHash(iWindow.location, fragment, options.replace);
}
diff --git a/test/collection.js b/test/collection.js
index 507191e3..fa848186 100644
--- a/test/collection.js
+++ b/test/collection.js
@@ -561,31 +561,17 @@
});
- test("create with wait:true should not call collection.parse", 1, function() {
- var collectionParseCalled = false;
-
- var Model = Backbone.Model.extend({
- sync: function (method, model, options) {
- _.extend(options, {specialSync: true});
- return Backbone.Model.prototype.sync.call(this, method, model, options);
- }
- });
-
+ test("create with wait:true should not call collection.parse", 0, function() {
var Collection = Backbone.Collection.extend({
- model: Model,
url: '/test',
parse: function () {
- collectionParseCalled = true;
+ ok(false);
}
});
var collection = new Collection;
- var success = function (model, response, options) {
- equal(collectionParseCalled, false);
- };
-
- collection.create({}, {wait: true, success: success});
+ collection.create({}, {wait: true});
this.ajaxSettings.success();
});
From e81474880b918e18d16de203874d9535775c4846 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 00:37:13 -0400
Subject: [PATCH 051/235] Don't bother with Collection#set unless models
---
backbone.js | 2 ++
test/collection.js | 4 ++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/backbone.js b/backbone.js
index 945a5458..e1a2920e 100644
--- a/backbone.js
+++ b/backbone.js
@@ -788,6 +788,8 @@
// already exist in the collection, as necessary. Similar to **Model#set**,
// the core operation for updating the data contained by the collection.
set: function(models, options) {
+ if (models == null) return;
+
options = _.defaults({}, options, setOptions);
if (options.parse && !this._isModel(models)) models = this.parse(models, options);
var singular = !_.isArray(models);
diff --git a/test/collection.js b/test/collection.js
index fa848186..7d56bb64 100644
--- a/test/collection.js
+++ b/test/collection.js
@@ -506,7 +506,7 @@
};
collection.url = '/test';
collection.fetch();
- this.syncArgs.options.success();
+ this.syncArgs.options.success([]);
equal(counter, 1);
});
@@ -1235,7 +1235,7 @@
}));
var ajax = Backbone.ajax;
Backbone.ajax = function (params) {
- _.defer(params.success);
+ _.defer(params.success, []);
return {someHeader: 'headerValue'};
};
collection.fetch({
From 1b8c9b462792ab820d18b2750778cda422f25c60 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 00:51:43 -0400
Subject: [PATCH 052/235] Declare variables as they are used
---
backbone.js | 38 +++++++++++++++++++++++++-------------
1 file changed, 25 insertions(+), 13 deletions(-)
diff --git a/backbone.js b/backbone.js
index e1a2920e..57313ef4 100644
--- a/backbone.js
+++ b/backbone.js
@@ -792,30 +792,42 @@
options = _.defaults({}, options, setOptions);
if (options.parse && !this._isModel(models)) models = this.parse(models, options);
+
var singular = !_.isArray(models);
models = singular ? (models ? [models] : []) : models.slice();
- var id, model, attrs, existing, sort;
+
var at = options.at;
if (at != null) at = +at;
if (at < 0) at += this.length + 1;
+
+ var toAdd = [];
+ var toRemove = [];
+ var modelMap = {};
+
+ var add = options.add;
+ var merge = options.merge;
+ var remove = options.remove;
+
+ var sort = false;
var sortable = this.comparator && (at == null) && options.sort !== false;
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
- var toAdd = [], toRemove = [], modelMap = {};
- var add = options.add, merge = options.merge, remove = options.remove;
+
var order = !sortable && add && remove ? [] : false;
var orderChanged = false;
// Turn bare objects into model references, and prevent invalid models
// from being added.
+ var model;
for (var i = 0; i < models.length; i++) {
- attrs = models[i];
+ model = models[i];
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
- if (existing = this.get(attrs)) {
+ var existing;
+ if (existing = this.get(model)) {
if (remove) modelMap[existing.cid] = true;
- if (merge && attrs !== existing) {
- attrs = this._isModel(attrs) ? attrs.attributes : attrs;
+ if (merge && model !== existing) {
+ var attrs = this._isModel(model) ? model.attributes : model;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
@@ -824,7 +836,7 @@
// If this is a new, valid model, push it to the `toAdd` list.
} else if (add) {
- model = models[i] = this._prepareModel(attrs, options);
+ model = models[i] = this._prepareModel(model, options);
if (!model) continue;
toAdd.push(model);
this._addReference(model, options);
@@ -833,7 +845,7 @@
// Do not add multiple models with the same `id`.
model = existing || model;
if (!model) continue;
- id = this.modelId(model.attributes);
+ var id = this.modelId(model.attributes);
if (order && (model.isNew() || !modelMap[id])) {
order.push(model);
@@ -846,7 +858,7 @@
// Remove nonexistent models if appropriate.
if (remove) {
- for (var i = 0; i < this.length; i++) {
+ for (i = 0; i < this.length; i++) {
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
}
if (toRemove.length) this._removeModels(toRemove, options);
@@ -857,13 +869,13 @@
if (sortable) sort = true;
this.length += toAdd.length;
if (at != null) {
- for (var i = 0; i < toAdd.length; i++) {
+ for (i = 0; i < toAdd.length; i++) {
this.models.splice(at + i, 0, toAdd[i]);
}
} else {
if (order) this.models.length = 0;
var orderedModels = order || toAdd;
- for (var i = 0; i < orderedModels.length; i++) {
+ for (i = 0; i < orderedModels.length; i++) {
this.models.push(orderedModels[i]);
}
}
@@ -875,7 +887,7 @@
// Unless silenced, it's time to fire all appropriate add/sort events.
if (!options.silent) {
var addOpts = at != null ? _.clone(options) : options;
- for (var i = 0; i < toAdd.length; i++) {
+ for (i = 0; i < toAdd.length; i++) {
if (at != null) addOpts.index = at + i;
(model = toAdd[i]).trigger('add', model, this, addOpts);
}
From 496d0838881d01b02c1dda75d32535b5669bf805 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 00:53:09 -0400
Subject: [PATCH 053/235] Ungolf assignments
---
backbone.js | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/backbone.js b/backbone.js
index 57313ef4..f51e20d7 100644
--- a/backbone.js
+++ b/backbone.js
@@ -823,8 +823,8 @@
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
- var existing;
- if (existing = this.get(model)) {
+ var existing = this.get(model);
+ if (existing) {
if (remove) modelMap[existing.cid] = true;
if (merge && model !== existing) {
var attrs = this._isModel(model) ? model.attributes : model;
@@ -859,7 +859,8 @@
// Remove nonexistent models if appropriate.
if (remove) {
for (i = 0; i < this.length; i++) {
- if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
+ model = this.models[i];
+ if (!modelMap[model.cid]) toRemove.push(model);
}
if (toRemove.length) this._removeModels(toRemove, options);
}
@@ -888,8 +889,9 @@
if (!options.silent) {
var addOpts = at != null ? _.clone(options) : options;
for (i = 0; i < toAdd.length; i++) {
+ model = toAdd[i];
if (at != null) addOpts.index = at + i;
- (model = toAdd[i]).trigger('add', model, this, addOpts);
+ model.trigger('add', model, this, addOpts);
}
if (sort || orderChanged) this.trigger('sort', this, options);
if (toAdd.length || toRemove.length) this.trigger('update', this, options);
From 7902a2f13fdac59b32adf662040d879a803a1a97 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 00:54:12 -0400
Subject: [PATCH 054/235] Golf assignment
---
backbone.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index f51e20d7..5d977135 100644
--- a/backbone.js
+++ b/backbone.js
@@ -830,7 +830,7 @@
var attrs = this._isModel(model) ? model.attributes : model;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
- if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
+ if (sortable && !sort) sort = existing.hasChanged(sortAttr);
}
models[i] = existing;
From e42e3118ac86154e0791f9460a64e899c5954418 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 00:56:21 -0400
Subject: [PATCH 055/235] Invert ternary
---
backbone.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index 5d977135..583e064b 100644
--- a/backbone.js
+++ b/backbone.js
@@ -887,7 +887,7 @@
// Unless silenced, it's time to fire all appropriate add/sort events.
if (!options.silent) {
- var addOpts = at != null ? _.clone(options) : options;
+ var addOpts = at == null ? options : _.clone(options);
for (i = 0; i < toAdd.length; i++) {
model = toAdd[i];
if (at != null) addOpts.index = at + i;
From c105cc200f5b161ed1fcfa0c28ffcb0d56249b64 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 01:06:26 -0400
Subject: [PATCH 056/235] Invert conditional
---
backbone.js | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/backbone.js b/backbone.js
index 583e064b..24252809 100644
--- a/backbone.js
+++ b/backbone.js
@@ -869,16 +869,16 @@
if (toAdd.length || orderChanged) {
if (sortable) sort = true;
this.length += toAdd.length;
- if (at != null) {
- for (i = 0; i < toAdd.length; i++) {
- this.models.splice(at + i, 0, toAdd[i]);
- }
- } else {
+ if (at == null) {
if (order) this.models.length = 0;
var orderedModels = order || toAdd;
for (i = 0; i < orderedModels.length; i++) {
this.models.push(orderedModels[i]);
}
+ } else {
+ for (i = 0; i < toAdd.length; i++) {
+ this.models.splice(at + i, 0, toAdd[i]);
+ }
}
}
From a082d2fdf9d0509ae7082296e41a0b7efe6878d2 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 01:09:45 -0400
Subject: [PATCH 057/235] Return only added or merged models
Mirrors the change in #3512.
---
backbone.js | 53 +++++++++++++++++++++-------------------------
test/collection.js | 6 +++---
2 files changed, 27 insertions(+), 32 deletions(-)
diff --git a/backbone.js b/backbone.js
index 24252809..56d2ee6a 100644
--- a/backbone.js
+++ b/backbone.js
@@ -794,12 +794,13 @@
if (options.parse && !this._isModel(models)) models = this.parse(models, options);
var singular = !_.isArray(models);
- models = singular ? (models ? [models] : []) : models.slice();
+ models = singular ? [models] : models;
var at = options.at;
if (at != null) at = +at;
if (at < 0) at += this.length + 1;
+ var set = [];
var toAdd = [];
var toRemove = [];
var modelMap = {};
@@ -812,9 +813,6 @@
var sortable = this.comparator && (at == null) && options.sort !== false;
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
- var order = !sortable && add && remove ? [] : false;
- var orderChanged = false;
-
// Turn bare objects into model references, and prevent invalid models
// from being added.
var model;
@@ -825,35 +823,27 @@
// optionally merge it into the existing model.
var existing = this.get(model);
if (existing) {
- if (remove) modelMap[existing.cid] = true;
if (merge && model !== existing) {
var attrs = this._isModel(model) ? model.attributes : model;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
if (sortable && !sort) sort = existing.hasChanged(sortAttr);
}
- models[i] = existing;
+ if (!modelMap[existing.cid]) {
+ modelMap[existing.cid] = true;
+ set.push(existing);
+ }
// If this is a new, valid model, push it to the `toAdd` list.
} else if (add) {
- model = models[i] = this._prepareModel(model, options);
- if (!model) continue;
- toAdd.push(model);
- this._addReference(model, options);
+ model = this._prepareModel(model, options);
+ if (model) {
+ toAdd.push(model);
+ this._addReference(model, options);
+ modelMap[model.cid] = true;
+ set.push(model);
+ }
}
-
- // Do not add multiple models with the same `id`.
- model = existing || model;
- if (!model) continue;
- var id = this.modelId(model.attributes);
- if (order && (model.isNew() || !modelMap[id])) {
- order.push(model);
-
- // Check to see if this is actually a new model at this index.
- orderChanged = orderChanged || !this.models[i] || model.cid !== this.models[i].cid;
- }
-
- modelMap[id] = true;
}
// Remove nonexistent models if appropriate.
@@ -866,14 +856,19 @@
}
// See if sorting is needed, update `length` and splice in new models.
- if (toAdd.length || orderChanged) {
+ var orderChanged = false;
+ if (!sortable && add && remove) {
+ orderChanged = set.length != this.length || _.any(this.models, function(model, index) {
+ return model !== set[index];
+ });
+ this.models = set.slice();
+ this.length = set.length;
+ } else if (toAdd.length) {
if (sortable) sort = true;
this.length += toAdd.length;
if (at == null) {
- if (order) this.models.length = 0;
- var orderedModels = order || toAdd;
- for (i = 0; i < orderedModels.length; i++) {
- this.models.push(orderedModels[i]);
+ for (i = 0; i < toAdd.length; i++) {
+ this.models.push(toAdd[i]);
}
} else {
for (i = 0; i < toAdd.length; i++) {
@@ -898,7 +893,7 @@
}
// Return the added (or merged) model (or models).
- return singular ? models[0] : models;
+ return singular ? set[0] : set;
},
// When you have more items than you want to add or remove individually,
diff --git a/test/collection.js b/test/collection.js
index 7d56bb64..58b654b8 100644
--- a/test/collection.js
+++ b/test/collection.js
@@ -339,11 +339,11 @@
list = col.add([{id: 3}, {id: 6}], {validate: true});
equal(col.length, 3);
- equal(list[0], false);
- equal(list[1].get('id'), 6);
+ equal(list.length, 1);
+ equal(list[0].get('id'), 6);
var result = col.add({id: 6});
- equal(result.cid, list[1].cid);
+ equal(result.cid, list[0].cid);
result = col.remove({id: 6});
equal(col.length, 2);
From 198726d4984a8fc9f0b134e39b9c7065df603a1f Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 01:14:38 -0400
Subject: [PATCH 058/235] Make adding models with at **orders** faster
Splicing in a loop is the worst thing you can do:
http://jsperf.com/insert-array-into-another-array/4
---
backbone.js | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/backbone.js b/backbone.js
index 56d2ee6a..1ebe571c 100644
--- a/backbone.js
+++ b/backbone.js
@@ -865,16 +865,13 @@
this.length = set.length;
} else if (toAdd.length) {
if (sortable) sort = true;
- this.length += toAdd.length;
+ models = this.models;
if (at == null) {
- for (i = 0; i < toAdd.length; i++) {
- this.models.push(toAdd[i]);
- }
+ this.models = models.concat(toAdd);
} else {
- for (i = 0; i < toAdd.length; i++) {
- this.models.splice(at + i, 0, toAdd[i]);
- }
+ this.models = models.slice(0, at).concat(toAdd).concat(models.slice(at, this.length));
}
+ this.length += toAdd.length;
}
// Silently sort the collection if appropriate.
From 203052e16408fc821c4cc63361595de0aac3814e Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 03:54:50 -0400
Subject: [PATCH 059/235] Do not update models if nothing is set
---
backbone.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index 1ebe571c..9cb4750b 100644
--- a/backbone.js
+++ b/backbone.js
@@ -857,7 +857,7 @@
// See if sorting is needed, update `length` and splice in new models.
var orderChanged = false;
- if (!sortable && add && remove) {
+ if (set.length && !sortable && add && remove) {
orderChanged = set.length != this.length || _.any(this.models, function(model, index) {
return model !== set[index];
});
From 7ba78fe66d6aee4c2c5c33d37e15eaa1728929a1 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 03:55:12 -0400
Subject: [PATCH 060/235] Remove unnecessary options clone
---
backbone.js | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/backbone.js b/backbone.js
index 9cb4750b..e644ccc7 100644
--- a/backbone.js
+++ b/backbone.js
@@ -879,11 +879,10 @@
// Unless silenced, it's time to fire all appropriate add/sort events.
if (!options.silent) {
- var addOpts = at == null ? options : _.clone(options);
for (i = 0; i < toAdd.length; i++) {
+ if (at != null) options.index = at + i;
model = toAdd[i];
- if (at != null) addOpts.index = at + i;
- model.trigger('add', model, this, addOpts);
+ model.trigger('add', model, this, options);
}
if (sort || orderChanged) this.trigger('sort', this, options);
if (toAdd.length || toRemove.length) this.trigger('update', this, options);
From 9a15fcff8be219c8c411546c64114d4075409f5c Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Sun, 31 May 2015 13:16:19 -0400
Subject: [PATCH 061/235] Use a splicing for-loop for stabler performance
- [Base case (`#unshift`)](http://jsperf.com/insert-array-into-another-array/6)
- ["Normal" collection size](http://jsperf.com/insert-array-into-another-array/8)
- [Large collection](http://jsperf.com/insert-array-into-another-array/7)
---
backbone.js | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/backbone.js b/backbone.js
index e644ccc7..1ac336a1 100644
--- a/backbone.js
+++ b/backbone.js
@@ -41,7 +41,15 @@
var previousBackbone = root.Backbone;
// Create a local reference to a common array method we'll want to use later.
- var slice = [].slice;
+ var slice = Array.prototype.slice;
+ var splice = function(array, insert, at) {
+ var length = insert.length;
+ var result = Array(array.length + length);
+ for (var i = 0; i < at; i++) result[i] = array[i];
+ for (i = 0; i < length; i++) result[i + at] = insert[i];
+ for (i = at; i < array.length; i++) result[i + length] = array[i];
+ return result;
+ };
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '1.2.0';
@@ -865,12 +873,7 @@
this.length = set.length;
} else if (toAdd.length) {
if (sortable) sort = true;
- models = this.models;
- if (at == null) {
- this.models = models.concat(toAdd);
- } else {
- this.models = models.slice(0, at).concat(toAdd).concat(models.slice(at, this.length));
- }
+ this.models = splice(this.models, toAdd, at == null ? this.length : at);
this.length += toAdd.length;
}
From 0052c2fc5c8b49dd74d5fc8af62a6ce5d0d6ec10 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Tue, 2 Jun 2015 04:23:29 -0400
Subject: [PATCH 062/235] Keep backwards compatibility with v1
---
backbone.js | 38 +++++++++++++++++++++-----------------
test/collection.js | 6 +++---
2 files changed, 24 insertions(+), 20 deletions(-)
diff --git a/backbone.js b/backbone.js
index 1ac336a1..eef72686 100644
--- a/backbone.js
+++ b/backbone.js
@@ -42,14 +42,6 @@
// Create a local reference to a common array method we'll want to use later.
var slice = Array.prototype.slice;
- var splice = function(array, insert, at) {
- var length = insert.length;
- var result = Array(array.length + length);
- for (var i = 0; i < at; i++) result[i] = array[i];
- for (i = 0; i < length; i++) result[i + at] = insert[i];
- for (i = at; i < array.length; i++) result[i + length] = array[i];
- return result;
- };
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '1.2.0';
@@ -754,6 +746,15 @@
var setOptions = {add: true, remove: true, merge: true};
var addOptions = {add: true, remove: false};
+ // Splices `insert` into `array` at index `at`.
+ var splice = function(array, insert, at) {
+ var tail = Array(array.length - at);
+ var length = insert.length;
+ for (var i = 0; i < tail.length; i++) tail[i] = array[i + at];
+ for (i = 0; i < length; i++) array[i + at] = insert[i];
+ for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
+ };
+
// Define the Collection's inheritable methods.
_.extend(Collection.prototype, Events, {
@@ -802,7 +803,7 @@
if (options.parse && !this._isModel(models)) models = this.parse(models, options);
var singular = !_.isArray(models);
- models = singular ? [models] : models;
+ models = singular ? [models] : _.clone(models);
var at = options.at;
if (at != null) at = +at;
@@ -841,10 +842,11 @@
modelMap[existing.cid] = true;
set.push(existing);
}
+ models[i] = existing;
// If this is a new, valid model, push it to the `toAdd` list.
} else if (add) {
- model = this._prepareModel(model, options);
+ model = models[i] = this._prepareModel(model, options);
if (model) {
toAdd.push(model);
this._addReference(model, options);
@@ -865,16 +867,18 @@
// See if sorting is needed, update `length` and splice in new models.
var orderChanged = false;
- if (set.length && !sortable && add && remove) {
- orderChanged = set.length != this.length || _.any(this.models, function(model, index) {
+ var replace = !sortable && add && remove;
+ if (set.length && replace) {
+ orderChanged = this.length != set.length || _.any(this.models, function(model, index) {
return model !== set[index];
});
- this.models = set.slice();
- this.length = set.length;
+ this.models.length = 0;
+ splice(this.models, set, 0);
+ this.length = this.models.length;
} else if (toAdd.length) {
if (sortable) sort = true;
- this.models = splice(this.models, toAdd, at == null ? this.length : at);
- this.length += toAdd.length;
+ splice(this.models, toAdd, at == null ? this.length : at);
+ this.length = this.models.length;
}
// Silently sort the collection if appropriate.
@@ -892,7 +896,7 @@
}
// Return the added (or merged) model (or models).
- return singular ? set[0] : set;
+ return singular ? models[0] : models;
},
// When you have more items than you want to add or remove individually,
diff --git a/test/collection.js b/test/collection.js
index 58b654b8..7d56bb64 100644
--- a/test/collection.js
+++ b/test/collection.js
@@ -339,11 +339,11 @@
list = col.add([{id: 3}, {id: 6}], {validate: true});
equal(col.length, 3);
- equal(list.length, 1);
- equal(list[0].get('id'), 6);
+ equal(list[0], false);
+ equal(list[1].get('id'), 6);
var result = col.add({id: 6});
- equal(result.cid, list[0].cid);
+ equal(result.cid, list[1].cid);
result = col.remove({id: 6});
equal(col.length, 2);
From d1cc8b799cab71b2a1fa91f3c1df95d02c5f995a Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Wed, 3 Jun 2015 21:29:57 -0400
Subject: [PATCH 063/235] Use a simple slice
---
backbone.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backbone.js b/backbone.js
index eef72686..44966525 100644
--- a/backbone.js
+++ b/backbone.js
@@ -803,7 +803,7 @@
if (options.parse && !this._isModel(models)) models = this.parse(models, options);
var singular = !_.isArray(models);
- models = singular ? [models] : _.clone(models);
+ models = singular ? [models] : models.slice();
var at = options.at;
if (at != null) at = +at;
From 6215000047f8a06ce29e0f0af08ec2a229e10c72 Mon Sep 17 00:00:00 2001
From: Justin Ridgewell
Date: Wed, 3 Jun 2015 21:10:47 -0400
Subject: [PATCH 064/235] Ungolf #create assignment
---
backbone.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/backbone.js b/backbone.js
index 945a5458..4ad9f668 100644
--- a/backbone.js
+++ b/backbone.js
@@ -983,8 +983,7 @@
// collection when they arrive. If `reset: true` is passed, the response
// data will be passed through the `reset` method instead of `set`.
fetch: function(options) {
- options = options ? _.clone(options) : {};
- if (options.parse === void 0) options.parse = true;
+ options = _.extend({parse: true}, options);
var success = options.success;
var collection = this;
options.success = function(resp) {
@@ -1003,7 +1002,8 @@
create: function(model, options) {
options = options ? _.clone(options) : {};
var wait = options.wait;
- if (!(model = this._prepareModel(model, options))) return false;
+ model = this._prepareModel(model, options);
+ if (!model) return false;
if (!wait) this.add(model, options);
var collection = this;
var success = options.success;
@@ -1060,7 +1060,6 @@
// Internal method called by both remove and set.
// Returns removed models, or false if nothing is removed.
_removeModels: function(models, options) {
- options = _.extend({}, options);
var removed = [];
for (var i = 0; i < models.length; i++) {
var model = this.get(models[i]);
From 2bfc84dfbc4ba8732a08d5eaade047b631001aca Mon Sep 17 00:00:00 2001
From: paulfalgout
Date: Wed, 3 Jun 2015 17:11:33 -0500
Subject: [PATCH 065/235] Draft changelog for Backbone 1.2.1
---
index.html | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/index.html b/index.html
index 32cbb9e7..ef2609f8 100644
--- a/index.html
+++ b/index.html
@@ -4237,6 +4237,37 @@ ActiveRecord::Base.include_root_in_json = false
Change Log
+ — Jun. 3, 2015
+ — Diff
+ — Docs
+
+
+ -
+ Collection#add now avoids trying to parse a model instance when passing parse: true.
+
+ -
+ Bug fix in Collection#remove.
+
+ -
+ Model#save no longer mutates the passed in attribute hash with the server's
+ response when passing wait: true.
+
+ -
+ Model#fetch no longer parses the response when passing patch: false.
+
+ -
+ Bug fix for iframe-based History when used with jsdom.
+
+ -
+ Bug fix where Collection#invoke was not taking additional arguments.
+
+ -
+ When using on with an event map, you can now pass the context as the
+ second argument. This was previously an undocumented behavior from 1.1.2 removed
+ in 1.2.0.
+
+
+
— May 13, 2015
— Diff
— Docs
From 938a8ff934fd4de4f0009f68d43f500f5920b490 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Thu, 4 Jun 2015 18:09:12 -0400
Subject: [PATCH 066/235] Backbone 1.2.1
---
backbone-min.js | 2 +-
backbone-min.map | 2 +-
backbone.js | 4 +-
bower.json | 2 +-
component.json | 2 +-
docs/backbone.html | 2078 ++++++++++++++++++++++----------------------
index.html | 14 +-
package.json | 2 +-
8 files changed, 1047 insertions(+), 1059 deletions(-)
diff --git a/backbone-min.js b/backbone-min.js
index d2e95b43..f29903b1 100644
--- a/backbone-min.js
+++ b/backbone-min.js
@@ -1,2 +1,2 @@
-(function(t){var e=typeof self=="object"&&self.self==self&&self||typeof global=="object"&&global.global==global&&global;if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){e.Backbone=t(e,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore"),r;try{r=require("jquery")}catch(s){}t(e,exports,i,r)}else{e.Backbone=t(e,{},e._,e.jQuery||e.Zepto||e.ender||e.$)}})(function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.slice;e.VERSION="1.2.0";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var o=e.Events={};var h=/\s+/;var u=function(t,e,r,s,n){var a=0,o;if(r&&typeof r==="object"){for(o=i.keys(r);a=1.7.0"
diff --git a/component.json b/component.json
index 9fb9099e..3418c4be 100644
--- a/component.json
+++ b/component.json
@@ -1,6 +1,6 @@
{
"name" : "backbone",
- "version" : "1.2.0",
+ "version" : "1.2.1",
"description" : "Give your JS App some Backbone with Models, Views, Collections, and Events.",
"keywords" : ["model", "view", "controller", "router", "server", "client", "browser"],
"repo" : "jashkenas/backbone",
diff --git a/docs/backbone.html b/docs/backbone.html
index 5d0e4699..7b536b1d 100644
--- a/docs/backbone.html
+++ b/docs/backbone.html
@@ -27,7 +27,7 @@
- Backbone.js 1.2.0
+ Backbone.js 1.2.1
@@ -187,12 +187,11 @@ restored later on, if noConflict is used.
- Create local references to array methods we’ll want to use later.
+ Create a local reference to a common array method we’ll want to use later.
- var array = [];
- var slice = array.slice;
+
@@ -207,7 +206,7 @@ restored later on, if noConflict is used.
- Backbone.VERSION = '1.2.0';
+ Backbone.VERSION = '1.2.1';
@@ -288,625 +287,6 @@ form param named model.
- Backbone.Events
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
A module that can be mixed in to any object in order to provide it with
-custom events. You may bind with on or remove with off callback
-functions to an event; trigger-ing an event fires all callbacks in
-succession.
-
var object = {};
-_.extend(object, Backbone.Events);
-object.on('expand', function(){ alert('expanded'); });
-object.trigger('expand');
-
-
-
- var Events = Backbone.Events = {};
-
-
-
-
-
-
-
-
-
Regular expression used to split event strings.
-
-
-
- var eventSplitter = /\s+/;
-
-
-
-
-
-
-
-
-
Iterates over the standard event, callback (as well as the fancy multiple
-space-separated events "change blur", callback and jQuery-style event
-maps {event: callback}), reducing them by manipulating memo.
-Passes a normalized single event name and callback, as well as any
-optional opts.
-
-
-
- var eventsApi = function(iteratee, memo, name, callback, opts) {
- var i = 0, names;
- if (name && typeof name === 'object') {
-
-
-
-
-
-
-
-
-
Handle event maps.
-
-
-
- for (names = _.keys(name); i < names.length ; i++) {
- memo = iteratee(memo, names[i], name[names[i]], opts);
- }
- } else if (name && eventSplitter.test(name)) {
-
-
-
-
-
-
-
-
-
Handle space separated event names.
-
-
-
- for (names = name.split(eventSplitter); i < names.length; i++) {
- memo = iteratee(memo, names[i], callback, opts);
- }
- } else {
- memo = iteratee(memo, name, callback, opts);
- }
- return memo;
- };
-
-
-
-
-
-
-
-
-
Bind an event to a callback function. Passing "all" will bind
-the callback to all events fired.
-
-
-
- Events.on = function(name, callback, context) {
- return internalOn(this, name, callback, context);
- };
-
-
-
-
-
-
-
-
-
An internal use on function, used to guard the listening argument from
-the public API.
-
-
-
- var internalOn = function(obj, name, callback, context, listening) {
- obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
- context: context,
- ctx: obj,
- listening: listening
- });
-
- if (listening) {
- var listeners = obj._listeners || (obj._listeners = {});
- listeners[listening.id] = listening;
- }
-
- return obj;
- };
-
-
-
-
-
-
-
-
-
Inversion-of-control versions of on. Tell this object to listen to
-an event in another object… keeping track of what it’s listening to.
-
-
-
- Events.listenTo = function(obj, name, callback) {
- if (!obj) return this;
- var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
- var listeningTo = this._listeningTo || (this._listeningTo = {});
- var listening = listeningTo[id];
-
-
-
-
-
-
-
-
-
This object is not listening to any other events on obj yet.
-Setup the necessary references to track the listening callbacks.
-
-
-
- if (!listening) {
- var thisId = this._listenId || (this._listenId = _.uniqueId('l'));
- listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0};
- }
-
-
-
-
-
-
-
-
-
Bind callbacks on obj, and keep track of them on listening.
-
-
-
- internalOn(obj, name, callback, this, listening);
- return this;
- };
-
-
-
-
-
-
-
-
-
The reducing API that adds a callback to the events object.
-
-
-
- var onApi = function(events, name, callback, options) {
- if (callback) {
- var handlers = events[name] || (events[name] = []);
- var context = options.context, ctx = options.ctx, listening = options.listening;
- if (listening) listening.count++;
-
- handlers.push({ callback: callback, context: context, ctx: context || ctx, listening: listening });
- }
- return events;
- };
-
-
-
-
-
-
-
-
-
Remove one or many callbacks. If context is null, removes all
-callbacks with that function. If callback is null, removes all
-callbacks for the event. If name is null, removes all bound
-callbacks for all events.
-
-
-
- Events.off = function(name, callback, context) {
- if (!this._events) return this;
- this._events = eventsApi(offApi, this._events, name, callback, {
- context: context,
- listeners: this._listeners
- });
- return this;
- };
-
-
-
-
-
-
-
-
-
Tell this object to stop listening to either specific events … or
-to every object it’s currently listening to.
-
-
-
- Events.stopListening = function(obj, name, callback) {
- var listeningTo = this._listeningTo;
- if (!listeningTo) return this;
-
- var ids = obj ? [obj._listenId] : _.keys(listeningTo);
-
- for (var i = 0; i < ids.length; i++) {
- var listening = listeningTo[ids[i]];
-
-
-
-
-
-
-
-
-
If listening doesn’t exist, this object is not currently
-listening to obj. Break out early.
-
-
-
- if (!listening) break;
-
- listening.obj.off(name, callback, this);
- }
- if (_.isEmpty(listeningTo)) this._listeningTo = void 0;
-
- return this;
- };
-
-
-
-
-
-
-
-
-
The reducing API that removes a callback from the events object.
-
-
-
- var offApi = function(events, name, callback, options) {
-
-
-
-
-
-
-
-
-
No events to consider.
-
-
-
- if (!events) return;
-
- var i = 0, length, listening;
- var context = options.context, listeners = options.listeners;
-
-
-
-
-
-
-
-
-
Delete all events listeners and “drop” events.
-
-
-
- if (!name && !callback && !context) {
- var ids = _.keys(listeners);
- for (; i < ids.length; i++) {
- listening = listeners[ids[i]];
- delete listeners[listening.id];
- delete listening.listeningTo[listening.objId];
- }
- return;
- }
-
- var names = name ? [name] : _.keys(events);
- for (; i < names.length; i++) {
- name = names[i];
- var handlers = events[name];
-
-
-
-
-
-
-
-
-
Bail out if there are no events stored.
-
-
-
-
-
-
-
-
-
-
-
-
-
Replace events if there are any remaining. Otherwise, clean up.
-
-
-
- var remaining = [];
- for (var j = 0; j < handlers.length; j++) {
- var handler = handlers[j];
- if (
- callback && callback !== handler.callback &&
- callback !== handler.callback._callback ||
- context && context !== handler.context
- ) {
- remaining.push(handler);
- } else {
- listening = handler.listening;
- if (listening && --listening.count === 0) {
- delete listeners[listening.id];
- delete listening.listeningTo[listening.objId];
- }
- }
- }
-
-
-
-
-
-
-
-
-
Update tail event if the list has any events. Otherwise, clean up.
-
-
-
- if (remaining.length) {
- events[name] = remaining;
- } else {
- delete events[name];
- }
- }
- if (_.size(events)) return events;
- };
-
-
-
-
-
-
-
-
-
Bind an event to only be triggered a single time. After the first time
-the callback is invoked, it will be removed. When multiple events are
-passed in using the space-separated syntax, the event will fire once for every
-event you passed in, not once for a combination of all events
-
-
-
- Events.once = function(name, callback, context) {
-
-
-
-
-
-
-
-
-
Map the event into a {event: once} object.
-
-
-
- var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
- return this.on(events, void 0, context);
- };
-
-
-
-
-
-
-
-
-
Inversion-of-control versions of once.
-
-
-
- Events.listenToOnce = function(obj, name, callback) {
-
-
-
-
-
-
-
-
-
Map the event into a {event: once} object.
-
-
-
- var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj));
- return this.listenTo(obj, events);
- };
-
-
-
-
-
-
-
-
-
Reduces the event callbacks into a map of {event: onceWrapper}.
-offer unbinds the onceWrapper after it as been called.
-
-
-
- var onceMap = function(map, name, callback, offer) {
- if (callback) {
- var once = map[name] = _.once(function() {
- offer(name, once);
- callback.apply(this, arguments);
- });
- once._callback = callback;
- }
- return map;
- };
-
-
-
-
-
-
-
-
-
Trigger one or many events, firing all bound callbacks. Callbacks are
-passed the same arguments as trigger is, apart from the event name
-(unless you’re listening on "all", which will cause your callback to
-receive the true name of the event as the first argument).
-
-
-
- Events.trigger = function(name) {
- if (!this._events) return this;
-
- var length = Math.max(0, arguments.length - 1);
- var args = Array(length);
- for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
-
- eventsApi(triggerApi, this._events, name, void 0, args);
- return this;
- };
-
-
-
-
-
-
-
-
-
Handles triggering the appropriate event callbacks.
-
-
-
- var triggerApi = function(objEvents, name, cb, args) {
- if (objEvents) {
- var events = objEvents[name];
- var allEvents = objEvents.all;
- if (events && allEvents) allEvents = allEvents.slice();
- if (events) triggerEvents(events, args);
- if (allEvents) triggerEvents(allEvents, [name].concat(args));
- }
- return objEvents;
- };
-
-
-
-
-
-
-
-
-
A difficult-to-believe, but optimized internal dispatch function for
-triggering events. Tries to keep the usual cases speedy (most internal
-Backbone events have 3 arguments).
-
-
-
- var triggerEvents = function(events, args) {
- var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
- switch (args.length) {
- case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
- case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
- case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
- case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
- default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
- }
- };
-
-
-
-
-
-
-
-
Proxy Underscore methods to a Backbone class’ prototype using a
particular attribute as the data argument
@@ -942,6 +322,626 @@ particular attribute as the data argument
+
+
+
+
+
Backbone.Events
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
A module that can be mixed in to any object in order to provide it with
+custom events. You may bind with on or remove with off callback
+functions to an event; trigger-ing an event fires all callbacks in
+succession.
+
var object = {};
+_.extend(object, Backbone.Events);
+object.on('expand', function(){ alert('expanded'); });
+object.trigger('expand');
+
+
+
+ var Events = Backbone.Events = {};
+
+
+
+
+
+
+
+
+
Regular expression used to split event strings.
+
+
+
+ var eventSplitter = /\s+/;
+
+
+
+
+
+
+
+
+
Iterates over the standard event, callback (as well as the fancy multiple
+space-separated events "change blur", callback and jQuery-style event
+maps {event: callback}), reducing them by manipulating memo.
+Passes a normalized single event name and callback, as well as any
+optional opts.
+
+
+
+ var eventsApi = function(iteratee, memo, name, callback, opts) {
+ var i = 0, names;
+ if (name && typeof name === 'object') {
+
+
+
+
+
+
+
+
+
Handle event maps.
+
+
+
+ if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
+ for (names = _.keys(name); i < names.length ; i++) {
+ memo = iteratee(memo, names[i], name[names[i]], opts);
+ }
+ } else if (name && eventSplitter.test(name)) {
+
+
+
+
+
+
+
+
+
Handle space separated event names.
+
+
+
+ for (names = name.split(eventSplitter); i < names.length; i++) {
+ memo = iteratee(memo, names[i], callback, opts);
+ }
+ } else {
+ memo = iteratee(memo, name, callback, opts);
+ }
+ return memo;
+ };
+
+
+
+
+
+
+
+
+
Bind an event to a callback function. Passing "all" will bind
+the callback to all events fired.
+
+
+
+ Events.on = function(name, callback, context) {
+ return internalOn(this, name, callback, context);
+ };
+
+
+
+
+
+
+
+
+
An internal use on function, used to guard the listening argument from
+the public API.
+
+
+
+ var internalOn = function(obj, name, callback, context, listening) {
+ obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
+ context: context,
+ ctx: obj,
+ listening: listening
+ });
+
+ if (listening) {
+ var listeners = obj._listeners || (obj._listeners = {});
+ listeners[listening.id] = listening;
+ }
+
+ return obj;
+ };
+
+
+
+
+
+
+
+
+
Inversion-of-control versions of on. Tell this object to listen to
+an event in another object… keeping track of what it’s listening to.
+
+
+
+ Events.listenTo = function(obj, name, callback) {
+ if (!obj) return this;
+ var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
+ var listeningTo = this._listeningTo || (this._listeningTo = {});
+ var listening = listeningTo[id];
+
+
+
+
+
+
+
+
+
This object is not listening to any other events on obj yet.
+Setup the necessary references to track the listening callbacks.
+
+
+
+ if (!listening) {
+ var thisId = this._listenId || (this._listenId = _.uniqueId('l'));
+ listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0};
+ }
+
+
+
+
+
+
+
+
+
Bind callbacks on obj, and keep track of them on listening.
+
+
+
+ internalOn(obj, name, callback, this, listening);
+ return this;
+ };
+
+
+
+
+
+
+
+
+
The reducing API that adds a callback to the events object.
+
+
+
+ var onApi = function(events, name, callback, options) {
+ if (callback) {
+ var handlers = events[name] || (events[name] = []);
+ var context = options.context, ctx = options.ctx, listening = options.listening;
+ if (listening) listening.count++;
+
+ handlers.push({ callback: callback, context: context, ctx: context || ctx, listening: listening });
+ }
+ return events;
+ };
+
+
+
+
+
+
+
+
+
Remove one or many callbacks. If context is null, removes all
+callbacks with that function. If callback is null, removes all
+callbacks for the event. If name is null, removes all bound
+callbacks for all events.
+
+
+
+ Events.off = function(name, callback, context) {
+ if (!this._events) return this;
+ this._events = eventsApi(offApi, this._events, name, callback, {
+ context: context,
+ listeners: this._listeners
+ });
+ return this;
+ };
+
+
+
+
+
+
+
+
+
Tell this object to stop listening to either specific events … or
+to every object it’s currently listening to.
+
+
+
+ Events.stopListening = function(obj, name, callback) {
+ var listeningTo = this._listeningTo;
+ if (!listeningTo) return this;
+
+ var ids = obj ? [obj._listenId] : _.keys(listeningTo);
+
+ for (var i = 0; i < ids.length; i++) {
+ var listening = listeningTo[ids[i]];
+
+
+
+
+
+
+
+
+
If listening doesn’t exist, this object is not currently
+listening to obj. Break out early.
+
+
+
+ if (!listening) break;
+
+ listening.obj.off(name, callback, this);
+ }
+ if (_.isEmpty(listeningTo)) this._listeningTo = void 0;
+
+ return this;
+ };
+
+
+
+
+
+
+
+
+
The reducing API that removes a callback from the events object.
+
+
+
+ var offApi = function(events, name, callback, options) {
+
+
+
+
+
+
+
+
+
No events to consider.
+
+
+
+ if (!events) return;
+
+ var i = 0, listening;
+ var context = options.context, listeners = options.listeners;
+
+
+
+
+
+
+
+
+
Delete all events listeners and “drop” events.
+
+
+
+ if (!name && !callback && !context) {
+ var ids = _.keys(listeners);
+ for (; i < ids.length; i++) {
+ listening = listeners[ids[i]];
+ delete listeners[listening.id];
+ delete listening.listeningTo[listening.objId];
+ }
+ return;
+ }
+
+ var names = name ? [name] : _.keys(events);
+ for (; i < names.length; i++) {
+ name = names[i];
+ var handlers = events[name];
+
+
+
+
+
+
+
+
+
Bail out if there are no events stored.
+
+
+
+
+
+
+
+
+
+
+
+
+
Replace events if there are any remaining. Otherwise, clean up.
+
+
+
+ var remaining = [];
+ for (var j = 0; j < handlers.length; j++) {
+ var handler = handlers[j];
+ if (
+ callback && callback !== handler.callback &&
+ callback !== handler.callback._callback ||
+ context && context !== handler.context
+ ) {
+ remaining.push(handler);
+ } else {
+ listening = handler.listening;
+ if (listening && --listening.count === 0) {
+ delete listeners[listening.id];
+ delete listening.listeningTo[listening.objId];
+ }
+ }
+ }
+
+
+
+
+
+
+
+
+
Update tail event if the list has any events. Otherwise, clean up.
+
+
+
+ if (remaining.length) {
+ events[name] = remaining;
+ } else {
+ delete events[name];
+ }
+ }
+ if (_.size(events)) return events;
+ };
+
+
+
+
+
+
+
+
+
Bind an event to only be triggered a single time. After the first time
+the callback is invoked, it will be removed. When multiple events are
+passed in using the space-separated syntax, the event will fire once for every
+event you passed in, not once for a combination of all events
+
+
+
+ Events.once = function(name, callback, context) {
+
+
+
+
+
+
+
+
+
Map the event into a {event: once} object.
+
+
+
+ var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
+ return this.on(events, void 0, context);
+ };
+
+
+
+
+
+
+
+
+
Inversion-of-control versions of once.
+
+
+
+ Events.listenToOnce = function(obj, name, callback) {
+
+
+
+
+
+
+
+
+
Map the event into a {event: once} object.
+
+
+
+ var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj));
+ return this.listenTo(obj, events);
+ };
+
+
+
+
+
+
+
+
+
Reduces the event callbacks into a map of {event: onceWrapper}.
+offer unbinds the onceWrapper after it has been called.
+
+
+
+ var onceMap = function(map, name, callback, offer) {
+ if (callback) {
+ var once = map[name] = _.once(function() {
+ offer(name, once);
+ callback.apply(this, arguments);
+ });
+ once._callback = callback;
+ }
+ return map;
+ };
+
+
+
+
+
+
+
+
+
Trigger one or many events, firing all bound callbacks. Callbacks are
+passed the same arguments as trigger is, apart from the event name
+(unless you’re listening on "all", which will cause your callback to
+receive the true name of the event as the first argument).
+
+
+
+ Events.trigger = function(name) {
+ if (!this._events) return this;
+
+ var length = Math.max(0, arguments.length - 1);
+ var args = Array(length);
+ for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
+
+ eventsApi(triggerApi, this._events, name, void 0, args);
+ return this;
+ };
+
+
+
+
+
+
+
+
+
Handles triggering the appropriate event callbacks.
+
+
+
+ var triggerApi = function(objEvents, name, cb, args) {
+ if (objEvents) {
+ var events = objEvents[name];
+ var allEvents = objEvents.all;
+ if (events && allEvents) allEvents = allEvents.slice();
+ if (events) triggerEvents(events, args);
+ if (allEvents) triggerEvents(allEvents, [name].concat(args));
+ }
+ return objEvents;
+ };
+
+
+
+
+
+
+
+
+
A difficult-to-believe, but optimized internal dispatch function for
+triggering events. Tries to keep the usual cases speedy (most internal
+Backbone events have 3 arguments).
+
+
+
+ var triggerEvents = function(events, args) {
+ var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
+ switch (args.length) {
+ case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
+ case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
+ case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
+ case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
+ default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
+ }
+ };
+
+
+
+
@@ -1252,7 +1252,6 @@ anyone who needs to know about the change in state. The heart of the beast.
set: function(key, val, options) {
- var attr, attrs, unset, changes, silent, changing, prev, current;
if (key == null) return this;
@@ -1268,7 +1267,8 @@ anyone who needs to know about the change in state. The heart of the beast.
- if (typeof key === 'object') {
+ var attrs;
+ if (typeof key === 'object') {
attrs = key;
options = val;
} else {
@@ -1305,17 +1305,20 @@ anyone who needs to know about the change in state. The heart of the beast.
-
unset = options.unset;
- silent = options.silent;
- changes = [];
- changing = this._changing;
- this._changing = true;
+ var unset = options.unset;
+ var silent = options.silent;
+ var changes = [];
+ var changing = this._changing;
+ this._changing = true;
if (!changing) {
this._previousAttributes = _.clone(this.attributes);
this.changed = {};
}
- current = this.attributes, prev = this._previousAttributes;
+
+ var current = this.attributes;
+ var changed = this.changed;
+ var prev = this._previousAttributes;
@@ -1345,13 +1348,13 @@ anyone who needs to know about the change in state. The heart of the beast.
- for (attr in attrs) {
+ for (var attr in attrs) {
val = attrs[attr];
if (!_.isEqual(current[attr], val)) changes.push(attr);
if (!_.isEqual(prev[attr], val)) {
- this.changed[attr] = val;
+ changed[attr] = val;
} else {
- delete this.changed[attr];
+ delete changed[attr];
}
unset ? delete current[attr] : current[attr] = val;
}
@@ -1479,13 +1482,14 @@ determining if there would be a change.
changedAttributes: function(diff) {
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
- var val, changed = false;
var old = this._changing ? this._previousAttributes : this.attributes;
+ var changed = {};
for (var attr in diff) {
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
- (changed || (changed = {}))[attr] = val;
+ var val = diff[attr];
+ if (_.isEqual(old[attr], val)) continue;
+ changed[attr] = val;
}
- return changed;
+ return _.size(changed) ? changed : false;
},
@@ -1540,12 +1544,12 @@ local attributes. Any changed attributes will trigger a “change” event.
fetch: function(options) {
- options = options ? _.clone(options) : {};
- if (options.parse === void 0) options.parse = true;
+ options = _.extend({parse: true}, options);
var model = this;
var success = options.success;
options.success = function(resp) {
- if (!model.set(model.parse(resp, options), options)) return false;
+ var serverAttrs = options.parse ? model.parse(resp, options) : resp;
+ if (!model.set(serverAttrs, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
};
@@ -1568,8 +1572,7 @@ state will be set again.
-
save: function(key, val, options) {
- var attrs, method, xhr, attributes = this.attributes, wait;
+
save: function(key, val, options) {
@@ -1584,15 +1587,16 @@ state will be
set again.
-
if (key == null || typeof key === 'object') {
+ var attrs;
+ if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
- options = _.extend({validate: true}, options);
- wait = options.wait;
+ options = _.extend({validate: true, parse: true}, options);
+ var wait = options.wait;
@@ -1624,13 +1628,15 @@ the model will be valid when the attributes, if any, are set.
-
Set temporary attributes if {wait: true}.
+
After a successful server-side save, the client is (optionally)
+updated with the server-side state.
- if (attrs && wait) {
- this.attributes = _.extend({}, attributes, attrs);
- }
+ var model = this;
+ var success = options.success;
+ var attributes = this.attributes;
+ options.success = function(resp) {
@@ -1641,15 +1647,18 @@ the model will be valid when the attributes, if any, are set.
- After a successful server-side save, the client is (optionally)
-updated with the server-side state.
+ Ensure attributes are restored during synchronous saves.
-
if (options.parse === void 0) options.parse = true;
- var model = this;
- var success = options.success;
- options.success = function(resp) {
+
model.attributes = attributes;
+ var serverAttrs = options.parse ? model.parse(resp, options) : resp;
+ if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
+ if (serverAttrs && !model.set(serverAttrs, options)) return false;
+ if (success) success.call(options.context, model, resp, options);
+ model.trigger('sync', model, resp, options);
+ };
+ wrapError(this, options);
@@ -1660,24 +1669,15 @@ updated with the server-side state.
-
Ensure attributes are restored during synchronous saves.
+
Set temporary attributes if {wait: true} to properly find new ids.
- model.attributes = attributes;
- var serverAttrs = options.parse ? model.parse(resp, options) : resp;
- if (wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
- if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
- return false;
- }
- if (success) success.call(options.context, model, resp, options);
- model.trigger('sync', model, resp, options);
- };
- wrapError(this, options);
+ if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
- method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
+ var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch' && !options.attrs) options.attrs = attrs;
- xhr = this.sync(method, this, options);
+ var xhr = this.sync(method, this, options);
@@ -1692,7 +1692,7 @@ updated with the server-side state.
- if (attrs && wait) this.attributes = attributes;
+ this.attributes = attributes;
return xhr;
},
@@ -1761,8 +1761,8 @@ that will be called.
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
- var id = this.id || this.attributes[this.idAttribute];
- return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(id);
+ var id = this.get(this.idAttribute);
+ return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
},
@@ -1831,7 +1831,7 @@ the model. The default implementation is just to pass the response along.
isValid: function(options) {
- return this._validate({}, _.extend(options || {}, { validate: true }));
+ return this._validate({}, _.defaults({validate: true}, options));
},
@@ -2035,7 +2035,7 @@ models’ attributes.
toJSON: function(options) {
- return this.map(function(model){ return model.toJSON(options); });
+ return this.map(function(model) { return model.toJSON(options); });
},
@@ -2086,12 +2086,12 @@ models’ attributes.
remove: function(models, options) {
- var singular = !_.isArray(models), removed;
+ options = _.extend({}, options);
+ var singular = !_.isArray(models);
models = singular ? [models] : _.clone(models);
- options || (options = {});
- removed = this._removeModels(models, options);
+ var removed = this._removeModels(models, options);
if (!options.silent && removed) this.trigger('update', this, options);
- return singular ? models[0] : models;
+ return singular ? removed[0] : removed;
},
@@ -2112,7 +2112,7 @@ the core operation for updating the data contained by the collection.
set: function(models, options) {
options = _.defaults({}, options, setOptions);
- if (options.parse) models = this.parse(models, options);
+ if (options.parse && !this._isModel(models)) models = this.parse(models, options);
var singular = !_.isArray(models);
models = singular ? (models ? [models] : []) : models.slice();
var id, model, attrs, existing, sort;
@@ -2388,8 +2388,7 @@ Useful for bulk operations and optimizations.
pop: function(options) {
var model = this.at(this.length - 1);
- this.remove(model, options);
- return model;
+ return this.remove(model, options);
},
@@ -2424,8 +2423,7 @@ Useful for bulk operations and optimizations.
shift: function(options) {
var model = this.at(0);
- this.remove(model, options);
- return model;
+ return this.remove(model, options);
},
@@ -2596,8 +2594,7 @@ data will be passed through the reset method instead of set
fetch: function(options) {
- options = options ? _.clone(options) : {};
- if (options.parse === void 0) options.parse = true;
+ options = _.extend({parse: true}, options);
var success = options.success;
var collection = this;
options.success = function(resp) {
@@ -2628,7 +2625,8 @@ wait for the server to agree.
create: function(model, options) {
options = options ? _.clone(options) : {};
var wait = options.wait;
- if (!(model = this._prepareModel(model, options))) return false;
+ model = this._prepareModel(model, options);
+ if (!model) return false;
if (!wait) this.add(model, options);
var collection = this;
var success = options.success;
@@ -2751,30 +2749,31 @@ collection.
- Internal method called by both remove and set. Does not trigger any
-additional events. Returns true if anything was actually removed.
+ Internal method called by both remove and set.
+Returns removed models, or false if nothing is removed.
_removeModels: function(models, options) {
- var i, l, index, model, removed = false;
- for (var i = 0, j = 0; i < models.length; i++) {
- var model = models[i] = this.get(models[i]);
+ var removed = [];
+ for (var i = 0; i < models.length; i++) {
+ var model = this.get(models[i]);
if (!model) continue;
- var id = this.modelId(model.attributes);
- if (id != null) delete this._byId[id];
- delete this._byId[model.cid];
+
var index = this.indexOf(model);
this.models.splice(index, 1);
this.length--;
+
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
- models[j++] = model;
+
+ removed.push(model);
this._removeReference(model, options);
- removed = true;
- }
+ }
+
return removed.length ? removed :
false;
+ },
@@ -2785,24 +2784,6 @@ additional events. Returns true if anything was actually removed.
-
We only need to slice if models array should be smaller, which is
-caused by some models not actually getting removed.
-
-
-
-
if (models.length !== j) models = models.slice(0, j);
- return removed;
- },
-
-
-
-
-
-
-
-
Method for checking whether an object should be considered a model for
the purposes of adding to the collection.
@@ -2815,11 +2796,11 @@ the purposes of adding to the collection.
-
+
Internal method to create a model’s ties to a collection.
@@ -2835,17 +2816,20 @@ the purposes of adding to the collection.
-
+
Internal method to sever a model’s ties to a collection.
_removeReference: function(model, options) {
+ delete this._byId[model.cid];
+ var id = this.modelId(model.attributes);
+ if (id != null) delete this._byId[id];
if (this === model.collection) delete model.collection;
model.off('all', this._onModelEvent, this);
},
@@ -2853,11 +2837,11 @@ the purposes of adding to the collection.
-
+
Internal method called every time a model in the set fires an event.
Sets need to update their indexes when models change ids. All other
@@ -2885,11 +2869,11 @@ in other collections are ignored.
-
+
Underscore methods that we want to implement on the Collection.
90% of the core usefulness of Backbone Collections is actually implemented
@@ -2900,7 +2884,7 @@ right here:
var collectionMethods = { forEach: 3, each: 3, map: 3, collect: 3, reduce: 4,
foldl: 4, inject: 4, reduceRight: 4, foldr: 4, find: 3, detect: 3, filter: 3,
select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 2,
- contains: 2, invoke: 2, max: 3, min: 3, toArray: 1, size: 1, first: 3,
+ contains: 2, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
isEmpty: 1, chain: 1, sample: 3, partition: 3 };
@@ -2908,11 +2892,11 @@ right here:
-
+
Mix in each Underscore method as a proxy to Collection#models.
@@ -2923,11 +2907,11 @@ right here:
-
+
Underscore methods that take a property name as an argument.
@@ -2938,11 +2922,11 @@ right here:
-
+
Use attributes instead of properties.
@@ -2961,14 +2945,26 @@ right here:
+
+
+
+
+
+
@@ -2980,18 +2976,6 @@ right here:
-
-
-
-
-
-
-
-
-
-
Backbone Views are almost more convention than they are actual code. A View
is simply a JavaScript object that represents a logical chunk of UI in the
DOM. This might be a single item, an entire list, a sidebar or panel, or
@@ -3005,11 +2989,11 @@ react to specific changes in the state of your models.
-
+
Creating a Backbone.View creates its initial element outside of the DOM,
if an existing element is not provided…
@@ -3018,7 +3002,6 @@ if an existing element is not provided…
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
- options || (options = {});
_.extend(this, _.pick(options, viewOptions));
this._ensureElement();
this.initialize.apply(this, arguments);
@@ -3027,11 +3010,11 @@ if an existing element is not provided…
-
+
Cached regex to split keys for delegate.
@@ -3042,11 +3025,11 @@ if an existing element is not provided…
-
+
List of view options to be merged as properties.
@@ -3057,11 +3040,11 @@ if an existing element is not provided…
-
+
Set up all inheritable Backbone.View properties and methods.
@@ -3072,11 +3055,11 @@ if an existing element is not provided…
-
+
The default tagName of a View’s element is "div".
@@ -3087,11 +3070,11 @@ if an existing element is not provided…
-
+
jQuery delegate for element lookup, scoped to DOM elements within the
current view. This should be preferred to global lookups where possible.
@@ -3105,11 +3088,11 @@ current view. This should be preferred to global lookups where possible.
-
+
Initialize is an empty function by default. Override it with your own
initialization logic.
@@ -3121,11 +3104,11 @@ initialization logic.
-
+
render is the core function that your view should override, in order
to populate its element (this.el), with the appropriate HTML. The
@@ -3140,11 +3123,11 @@ convention is for render to always return this.
-
+
Remove this view by taking the element out of the DOM, and removing any
applicable Backbone.Events listeners.
@@ -3160,11 +3143,11 @@ applicable Backbone.Events listeners.
-
+
Remove this view’s element from the document and all event listeners
attached to it. Exposed for subclasses using an alternative DOM
@@ -3179,11 +3162,11 @@ manipulation API.
-
+
Change the view’s element (this.el property) and re-delegate the
view’s events on the new element.
@@ -3200,11 +3183,11 @@ view’s events on the new element.
-
+
Creates the this.el and this.$el references for this view using the
given el. el can be a CSS selector or an HTML string, a jQuery
@@ -3222,11 +3205,11 @@ alternative DOM manipulation API and are only required to set the
-
+
Set callbacks, where this.events is a hash of
{“event selector”: “callback”}
@@ -3242,11 +3225,12 @@ Omitting the selector binds the event to
this.el.
delegateEvents: function(events) {
- if (!(events || (events = _.result(this, 'events')))) return this;
+ events || (events = _.result(this, 'events'));
+ if (!events) return this;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
- if (!_.isFunction(method)) method = this[events[key]];
+ if (!_.isFunction(method)) method = this[method];
if (!method) continue;
var match = key.match(delegateEventSplitter);
this.delegate(match[1], match[2], _.bind(method, this));
@@ -3257,31 +3241,32 @@ Omitting the selector binds the event to this.el.
+
+
+
+
+
Add a single event listener to the view’s element (or a child element
+using selector). This only works for delegate-able events: not focus,
+blur, and not change, submit, and reset in Internet Explorer.
+
+
+
+ delegate: function(eventName, selector, listener) {
+ this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
+ return this;
+ },
+
+
+
+
-
Add a single event listener to the view’s element (or a child element
-using selector). This only works for delegate-able events: not focus,
-blur, and not change, submit, and reset in Internet Explorer.
-
-
-
- delegate: function(eventName, selector, listener) {
- this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
- },
-
-
-
-
-
-
-
-
Clears all callbacks previously bound to the view by delegateEvents.
You usually don’t need to use this, but may wish to if you have multiple
Backbone views attached to the same DOM element.
@@ -3296,11 +3281,11 @@ Backbone views attached to the same DOM element.
-
+
A finer-grained undelegateEvents for removing a single delegated event.
selector and listener are both optional.
@@ -3309,16 +3294,17 @@ Backbone views attached to the same DOM element.
undelegate: function(eventName, selector, listener) {
this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
+ return this;
},
-
+
Produces a DOM element to be assigned to your view. Exposed for
subclasses using an alternative DOM manipulation API.
@@ -3332,11 +3318,11 @@ subclasses using an alternative DOM manipulation API.
-
+
Ensure that the View has a DOM element to render into.
If this.el is a string, pass it through $(), take the first
@@ -3360,11 +3346,11 @@ an element from the id, className and tagName
-
+
Set attributes from a hash on this view’s element. Exposed for
subclasses using an alternative DOM manipulation API.
@@ -3380,14 +3366,26 @@ subclasses using an alternative DOM manipulation API.
+
+
+
+
+
+
@@ -3399,18 +3397,6 @@ subclasses using an alternative DOM manipulation API.
-
-
-
-
-
-
-
-
-
-
Override this function to change the manner in which Backbone persists
models to the server. You will be passed the type of request, and the
model in question. By default, makes a RESTful Ajax request
@@ -3435,11 +3421,11 @@ it difficult to read the body of PUT requests.
-
+
Default options, unless specified.
@@ -3453,11 +3439,11 @@ it difficult to read the body of
PUT requests.
-
+
Default JSON-request options.
@@ -3468,11 +3454,11 @@ it difficult to read the body of
PUT requests.
-
+
Ensure that we have a URL.
@@ -3485,11 +3471,11 @@ it difficult to read the body of
PUT requests.
-
+
Ensure that we have the appropriate request data.
@@ -3503,11 +3489,11 @@ it difficult to read the body of
PUT requests.
-
+
For older servers, emulate JSON by encoding the request into an HTML-form.
@@ -3521,11 +3507,11 @@ it difficult to read the body of
PUT requests.
-
+
For older servers, emulate HTTP by mimicking the HTTP method with _method
And an X-HTTP-Method-Override header.
@@ -3545,11 +3531,11 @@ And an
X-HTTP-Method-Override header.
-
+
Don’t process data on a non-GET request.
@@ -3562,11 +3548,11 @@ And an
X-HTTP-Method-Override header.
-
+
Pass along textStatus and errorThrown from jQuery.
@@ -3582,11 +3568,11 @@ And an
X-HTTP-Method-Override header.
-
+
Make the request, allowing the user to override any Ajax options.
@@ -3600,11 +3586,11 @@ And an
X-HTTP-Method-Override header.
-
+
Map from CRUD to HTTP for our default Backbone.sync implementation.
@@ -3621,11 +3607,11 @@ And an
X-HTTP-Method-Override header.
-
+
Set the default implementation of Backbone.ajax to proxy through to $.
Override this if you’d like to use a different library.
@@ -3639,14 +3625,26 @@ Override this if you’d like to use a different library.
+
+
+
+
+
Backbone.Router
+
+
+
+
+
+
@@ -3658,18 +3656,6 @@ Override this if you’d like to use a different library.
-
-
-
-
-
-
-
-
-
-
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.
@@ -3685,11 +3671,11 @@ matched. Creating a new one sets its
routes hash, if not set static
-
+
Cached regular expressions for matching named param parts and splatted
parts of route strings.
@@ -3704,11 +3690,11 @@ parts of route strings.
-
+
Set up all inheritable Backbone.Router properties and methods.
@@ -3719,11 +3705,11 @@ parts of route strings.
-
+
Initialize is an empty function by default. Override it with your own
initialization logic.
@@ -3735,11 +3721,11 @@ initialization logic.
-
+
Manually bind a single named route to a callback. For example:
this.route('search/:query/p:num', 'search', function(query, num) {
@@ -3770,11 +3756,11 @@ initialization logic.
-
+
Execute a route handler with the provided parameters. This is an
excellent place to do pre-route setup or post-route cleanup.
@@ -3788,11 +3774,11 @@ excellent place to do pre-route setup or post-route cleanup.
-
+
Simple proxy to Backbone.history to save a fragment into the history.
@@ -3806,11 +3792,11 @@ excellent place to do pre-route setup or post-route cleanup.
-
+
Bind all defined routes to Backbone.history. We have to reverse the
order of the routes here to support behavior where the most general
@@ -3830,11 +3816,11 @@ routes can be defined at the bottom of the route map.
-
+
Convert a route string into a regular expression, suitable for matching
against the current location hash.
@@ -3854,11 +3840,11 @@ against the current location hash.
-
+
Given a route, and a URL fragment that it matches, return the array of
extracted decoded parameters. Empty or unmatched parameters will be
@@ -3873,11 +3859,11 @@ treated as null to normalize cross-browser behavior.
-
+
Don’t decode the search params.
@@ -3893,14 +3879,26 @@ treated as
null to normalize cross-browser behavior.
+
+
+
+
+
Backbone.History
+
+
+
+
+
+
@@ -3912,18 +3910,6 @@ treated as
null to normalize cross-browser behavior.
-
-
-
-
-
-
-
-
-
-
Handles cross-browser history management, based on either
pushState and real URLs, or
onhashchange
@@ -3939,11 +3925,11 @@ falls back to polling.
-
+
Ensure that History can be used outside of the browser.
@@ -3958,11 +3944,11 @@ falls back to polling.
-
+
Cached regex for stripping a leading hash/slash and trailing space.
@@ -3973,11 +3959,11 @@ falls back to polling.
-
+
Cached regex for stripping leading and trailing slashes.
@@ -3988,11 +3974,11 @@ falls back to polling.
-
+
Cached regex for stripping urls of hash.
@@ -4003,11 +3989,11 @@ falls back to polling.
-
+
Has the history handling already been started?
@@ -4018,11 +4004,11 @@ falls back to polling.
-
+
Set up all inheritable Backbone.History properties and methods.
@@ -4033,11 +4019,11 @@ falls back to polling.
-
+
The default interval to poll for hash changes, if necessary, is
twenty times a second.
@@ -4049,11 +4035,11 @@ twenty times a second.
-
+
Are we at the app root?
@@ -4067,11 +4053,11 @@ twenty times a second.
-
+
Does the pathname match the root?
@@ -4086,11 +4072,11 @@ twenty times a second.
-
+
Unicode characters in location.pathname are percent encoded so they’re
decoded for comparison. %25 should not be decoded since it may be part
@@ -4105,11 +4091,11 @@ of an encoded parameter.
-
+
In IE6, the hash fragment and search params are incorrect if the
fragment contains ?.
@@ -4124,11 +4110,11 @@ fragment contains
?.
-
+
Gets the true hash value. Cannot use location.hash directly due to bug
in Firefox where location.hash will always be decoded.
@@ -4143,11 +4129,11 @@ in Firefox where location.hash will always be decoded.
-
+
Get the pathname and search params, without the root.
@@ -4163,11 +4149,11 @@ in Firefox where location.hash will always be decoded.
-
+
Get the cross-browser normalized URL fragment from the path or hash.
@@ -4187,11 +4173,11 @@ in Firefox where location.hash will always be decoded.
-
+
Start the hash change handling, returning true if the current URL matches
an existing route, and false otherwise.
@@ -4205,11 +4191,11 @@ an existing route, and
false otherwise.
-
+
Figure out the initial configuration. Do we need an iframe?
Is pushState desired … is it available?
@@ -4229,11 +4215,11 @@ Is pushState desired … is it available?
-
+
Normalize root to always include a leading and trailing slash.
@@ -4244,11 +4230,11 @@ Is pushState desired … is it available?
-
+
Transition from hashChange to pushState or vice versa if both are
requested.
@@ -4260,11 +4246,11 @@ requested.
-
+
If we’ve started off with a route from a pushState-enabled
browser, but we’re currently in a browser that doesn’t support it…
@@ -4278,11 +4264,11 @@ browser, but we’re currently in a browser that doesn’t support it…
-
+
Return immediately as browser will do redirect to new url
@@ -4293,11 +4279,11 @@ browser, but we’re currently in a browser that doesn’t support it…
-
+
Or if we’ve started out with a hash-based route, but we’re currently
in a browser where it could be pushState-based instead…
@@ -4313,11 +4299,11 @@ in a browser where it could be
pushState-based instead…
-
+
Proxy an iframe to handle location events if the browser doesn’t
support the hashchange event, HTML5 history, or the user wants
@@ -4326,39 +4312,40 @@ support the hashchange event, HTML5 history, or the user wants
if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
- var iframe = document.createElement('iframe');
- iframe.src = 'javascript:0';
- iframe.style.display = 'none';
- iframe.tabIndex = -1;
+ this.iframe = document.createElement('iframe');
+ this.iframe.src = 'javascript:0';
+ this.iframe.style.display = 'none';
+ this.iframe.tabIndex = -1;
var body = document.body;
+
+
+
+
+
Using appendChild will throw on IE < 9 if the document is not ready.
+
+
+
+ var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
+ iWindow.document.open();
+ iWindow.document.close();
+ iWindow.location.hash = '#' + this.fragment;
+ }
+
+
+
+
-
Using appendChild will throw on IE < 9 if the document is not ready.
-
-
-
- this.iframe = body.insertBefore(iframe, body.firstChild).contentWindow;
- this.iframe.document.open().close();
- this.iframe.location.hash = '#' + this.fragment;
- }
-
-
-
-
-
-
-
-
Add a cross-platform addEventListener shim for older browsers.
@@ -4370,11 +4357,11 @@ support the hashchange event, HTML5 history, or the user wants
-
+
Depending on whether we’re using pushState or hashes, and whether
‘onhashchange’ is supported, determine how we check the URL state.
@@ -4395,11 +4382,11 @@ support the
hashchange event, HTML5 history, or the user wants
-
+
Disable Backbone.history, perhaps temporarily. Not useful in a real app,
but possibly useful for unit testing Routers.
@@ -4411,11 +4398,11 @@ but possibly useful for unit testing Routers.
-
+
Add a cross-platform removeEventListener shim for older browsers.
@@ -4428,11 +4415,11 @@ but possibly useful for unit testing Routers.
-
+
Remove window listeners.
@@ -4447,29 +4434,29 @@ but possibly useful for unit testing Routers.
-
+
Clean up the iframe if necessary.
if (this.iframe) {
- document.body.removeChild(this.iframe.frameElement);
+ document.body.removeChild(this.iframe);
this.iframe = null;
}
-
+
Some environments will throw when clearing an undefined interval.
@@ -4482,11 +4469,11 @@ but possibly useful for unit testing Routers.
-
+
Add a route to be tested when the fragment changes. Routes added later
may override previous routes.
@@ -4500,11 +4487,11 @@ may override previous routes.
-
+
Checks the current URL to see if it has changed, and if it has,
calls loadUrl, normalizing across the hidden iframe.
@@ -4517,11 +4504,11 @@ calls
loadUrl, normalizing across the hidden iframe.
-
+
If the user pressed the back button, the iframe’s hash will have
changed and we should use that for comparison.
@@ -4529,7 +4516,7 @@ changed and we should use that for comparison.
if (current === this.fragment && this.iframe) {
- current = this.getHash(this.iframe);
+ current = this.getHash(this.iframe.contentWindow);
}
if (current === this.fragment) return false;
@@ -4540,11 +4527,11 @@ changed and we should use that for comparison.
-
+
Attempt to load the current URL fragment. If a route succeeds with a
match, returns true. If no defined routes matches the fragment,
@@ -4557,11 +4544,11 @@ returns false.
-
+
If the root doesn’t match, no routes can match either.
@@ -4580,11 +4567,11 @@ returns
false.
-
+
Save a fragment into the hash history, or replace the URL state if the
‘replace’ option is passed. You are responsible for properly URL-encoding
@@ -4602,11 +4589,11 @@ you wish to modify the current URL without adding an entry to the history.
-
+
Normalize the fragment.
@@ -4617,11 +4604,11 @@ you wish to modify the current URL without adding an entry to the history.
-
+
Don’t include a trailing slash on the root.
@@ -4636,11 +4623,11 @@ you wish to modify the current URL without adding an entry to the history.
-
+
Strip the hash and decode for matching.
@@ -4654,11 +4641,11 @@ you wish to modify the current URL without adding an entry to the history.
-
+
If pushState is available, we use it to set the fragment as a real URL.
@@ -4670,11 +4657,11 @@ you wish to modify the current URL without adding an entry to the history.
-
+
If hash changes haven’t been explicitly disabled, update the hash
fragment to store history.
@@ -4683,7 +4670,31 @@ fragment to store history.
} else if (this._wantsHashChange) {
this._updateHash(this.location, fragment, options.replace);
- if (this.iframe && (fragment !== this.getHash(this.iframe))) {
+
if (
this.iframe && (fragment !==
this.getHash(
this.iframe.contentWindow))) {
+
var iWindow =
this.iframe.contentWindow;
+
+
+
+
+
+
+
+
+
Opening and closing the iframe tricks IE7 and earlier to push a
+history entry on hash-tag change. When replace is true, we don’t
+want this.
+
+
+
+ if (!options.replace) {
+ iWindow.document.open();
+ iWindow.document.close();
+ }
+
+ this._updateHash(iWindow.location, fragment, options.replace);
+ }
@@ -4694,25 +4705,6 @@ fragment to store history.
-
Opening and closing the iframe tricks IE7 and earlier to push a
-history entry on hash-tag change. When replace is true, we don’t
-want this.
-
-
-
- if (!options.replace) this.iframe.document.open().close();
- this._updateHash(this.iframe.location, fragment, options.replace);
- }
-
-
-
-
-
-
-
-
If you’ve told us that you explicitly don’t want fallback hashchange-
based history, then navigate becomes a page refresh.
@@ -4727,11 +4719,11 @@ based history, then
navigate becomes a page refresh.
-
+
Update the hash location, either replacing the current entry, or adding
a new one to the browser history.
@@ -4747,11 +4739,11 @@ a new one to the browser history.
-
+
Some browsers require that hash contains a leading #.
@@ -4766,11 +4758,11 @@ a new one to the browser history.
-
+
Create the default Backbone.history.
@@ -4781,14 +4773,26 @@ a new one to the browser history.
+
+
+
+
+
+
@@ -4800,18 +4804,6 @@ a new one to the browser history.
-
-
-
-
-
-
-
-
-
-
Helper function to correctly set up the prototype chain for subclasses.
Similar to goog.inherits, but uses a hash of prototype properties and
class properties to be extended.
@@ -4825,11 +4817,11 @@ class properties to be extended.
-
+
The constructor function for the new subclass is either defined by you
(the “constructor” property in your extend definition), or defaulted
@@ -4846,11 +4838,11 @@ by us to simply call the parent constructor.
-
+
Add static properties to the constructor function, if supplied.
@@ -4861,11 +4853,11 @@ by us to simply call the parent constructor.
-
+
Set the prototype chain to inherit from parent, without calling
parent constructor function.
@@ -4879,11 +4871,11 @@ by us to simply call the parent constructor.
-
+
Add prototype properties (instance properties) to the subclass,
if supplied.
@@ -4895,11 +4887,11 @@ if supplied.
-
+
Set a convenience property in case the parent’s prototype is needed
later.
@@ -4914,11 +4906,11 @@ later.
-
+
Set up inheritance for the model, collection, router, view and history.
@@ -4929,11 +4921,11 @@ later.
-
+
Throw an error when a URL is needed, and none is supplied.
@@ -4946,11 +4938,11 @@ later.
-
+
Wrap an optional error callback with a fallback error event.
diff --git a/index.html b/index.html
index ef2609f8..e0375563 100644
--- a/index.html
+++ b/index.html
@@ -300,7 +300,7 @@