Update vendors, rebuild minified files, update docs/license.

Former-commit-id: 689793b6e5c4bbae917e726dc646902c697ce3a7
This commit is contained in:
John-David Dalton
2012-12-11 01:07:25 -08:00
parent 749f49b1a0
commit fe3e78cc1c
13 changed files with 605 additions and 268 deletions

View File

@@ -1,4 +1,4 @@
// Backbone.js 0.9.2
// Backbone.js 0.9.9-pre
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
@@ -34,7 +34,7 @@
}
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '0.9.2';
Backbone.VERSION = '0.9.9-pre';
// Require Underscore, if we're on the server, and it's not already present.
var _ = root._;
@@ -67,6 +67,9 @@
// Regular expression used to split event strings
var eventSplitter = /\s+/;
// Internal flag used to set event callbacks `once`.
var once = false;
// 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.
@@ -81,6 +84,13 @@
// Bind one or more space separated events, `events`, to a `callback`
// function. Passing `"all"` will bind the callback to all events fired.
on: function(events, callback, context) {
if (_.isObject(events)) {
for (var key in events) {
this.on(key, events[key], callback);
}
return this;
}
var calls, event, list;
if (!callback) return this;
@@ -89,16 +99,32 @@
while (event = events.shift()) {
list = calls[event] || (calls[event] = []);
list.push(callback, context);
list.push(callback, context, once ? {} : null);
}
return this;
},
// Bind events to only be triggered a single time. After the first time
// the callback is invoked, it will be removed.
once: function(events, callback, context) {
once = true;
this.on(events, callback, context);
once = false;
return this;
},
// 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 `events` is null, removes all bound callbacks for all events.
off: function(events, callback, context) {
if (_.isObject(events)) {
for (var key in events) {
this.off(key, events[key], callback);
}
return this;
}
var event, calls, list, i;
// No events, or removing *all* events.
@@ -117,9 +143,9 @@
continue;
}
for (i = list.length - 2; i >= 0; i -= 2) {
for (i = list.length - 3; i >= 0; i -= 3) {
if (!(callback && list[i] !== callback || context && list[i + 1] !== context)) {
list.splice(i, 2);
list.splice(i, 3);
}
}
}
@@ -132,7 +158,7 @@
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
trigger: function(events) {
var event, calls, list, i, length, args, all, rest;
var event, calls, list, i, length, args, all, rest, callback, context, onced;
if (!(calls = this._callbacks)) return this;
rest = [];
@@ -153,15 +179,18 @@
// Execute event callbacks.
if (list) {
for (i = 0, length = list.length; i < length; i += 2) {
list[i].apply(list[i + 1] || this, rest);
for (i = 0, length = list.length; i < length; i += 3) {
callback = list[i], context = list[i + 1], onced = list[i + 2];
if (onced) calls[event].splice(i, 3);
if (!onced || !onced.dead) callback.apply(context || this, rest);
if (onced) onced.dead = true;
}
}
// Execute "all" callbacks.
if (all) {
args = [event].concat(rest);
for (i = 0, length = all.length; i < length; i += 2) {
for (i = 0, length = all.length; i < length; i += 3) {
all[i].apply(all[i + 1] || this, args);
}
}
@@ -176,6 +205,10 @@
Events.bind = Events.on;
Events.unbind = Events.off;
// Allow the `Backbone` object to serve as a global event bus, for folks who
// want global "pubsub" in a convenient place.
_.extend(Backbone, Events);
// Backbone.Model
// --------------
@@ -390,7 +423,9 @@
};
// Finish configuring and sending the Ajax request.
var xhr = this.sync(this.isNew() ? 'create' : 'update', this, options);
var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method == 'patch') options.attrs = attrs;
var xhr = this.sync(method, this, options);
// When using `wait`, reset attributes to original values unless
// `success` has been called already.
@@ -565,7 +600,7 @@
// returning `true` if all is well. If a specific `error` callback has
// been passed, call that instead of firing the general `"error"` event.
_validate: function(attrs, options) {
if (options && options.silent || !this.validate) return true;
if (!this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options);
if (!error) return true;
@@ -588,10 +623,7 @@
if (options.comparator !== void 0) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
if (models) {
if (options.parse) models = this.parse(models);
this.reset(models, {silent: true, parse: options.parse});
}
if (models) this.reset(models, _.extend({silent: true}, options));
};
// Define the Collection's inheritable methods.
@@ -679,7 +711,7 @@
options || (options = {});
models = _.isArray(models) ? models.slice() : [models];
for (i = 0, l = models.length; i < l; i++) {
model = this.getByCid(models[i]) || this.get(models[i]);
model = this.get(models[i]);
if (!model) continue;
delete this._byId[model.id];
delete this._byCid[model.cid];
@@ -729,14 +761,9 @@
},
// Get a model from the set by id.
get: function(id) {
if (id == null) return void 0;
return this._byId[id.id != null ? id.id : id];
},
// Get a model from the set by client id.
getByCid: function(cid) {
return cid && this._byCid[cid.cid || cid];
get: function(obj) {
if (obj == null) return void 0;
return this._byId[obj.id != null ? obj.id : obj] || this._byCid[obj.cid || obj];
},
// Get the model at the given index.
@@ -769,7 +796,7 @@
this.models.sort(_.bind(this.comparator, this));
}
if (!options || !options.silent) this.trigger('reset', this, options);
if (!options || !options.silent) this.trigger('sort', this, options);
return this;
},
@@ -778,16 +805,46 @@
return _.invoke(this.models, 'get', attr);
},
// Smartly update a collection with a change set of models, adding,
// removing, and merging as necessary.
update: function(models, options) {
var model, i, l, id, cid, existing;
var add = [], remove = [];
options = _.extend({add: true, merge: true, remove: false}, options);
// Determine which models to add and merge, and which to remove.
for (i = 0, l = models.length; i < l; i++) {
model = models[i];
existing = this.get(model);
if (options.add || options.merge && existing) add.push(model);
}
if (options.remove) {
var changeset = new Collection(models);
for (i = 0, l = this.models.length; i < l; i++) {
model = this.models[i];
if (!changeset.get(model)) remove.push(model);
}
}
// Remove models (if applicable) before we add and merge the rest.
if (remove.length) this.remove(remove, options);
if (add.length) this.add(add, options);
return this;
},
// When you have more items than you want to add or remove individually,
// you can reset the entire set with a new list of models, without firing
// any `add` or `remove` events. Fires `reset` when finished.
reset: function(models, options) {
options || (options = {});
if (options.parse) models = this.parse(models);
for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
}
options.previousModels = this.models;
this._reset();
if (models) this.add(models, _.extend({silent: true}, options));
if (!options || !options.silent) this.trigger('reset', this, options);
if (!options.silent) this.trigger('reset', this, options);
return this;
},
@@ -800,7 +857,8 @@
var collection = this;
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
var method = options.update ? 'update' : 'reset';
collection[method](collection.parse(resp, xhr), options);
if (success) success(collection, resp, options);
};
return this.sync('read', this, options);
@@ -859,7 +917,7 @@
options || (options = {});
options.collection = this;
var model = new this.model(attrs, options);
if (!model._validate(model.attributes, options)) return false;
if (!model._validate(attrs, options)) return false;
return model;
},
@@ -932,7 +990,7 @@
var optionalParam = /\((.*?)\)/g;
var namedParam = /:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[-{}[\]+?.,\\^$|#\s]/g;
var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
// Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Router.prototype, Events, {
@@ -1051,7 +1109,7 @@
fragment = this.getHash();
}
}
return decodeURIComponent(fragment.replace(routeStripper, ''));
return fragment.replace(routeStripper, '');
},
// Start the hash change handling, returning `true` if the current URL matches
@@ -1364,6 +1422,7 @@
var methodMap = {
'create': 'POST',
'update': 'PUT',
'patch': 'PATCH',
'delete': 'DELETE',
'read': 'GET'
};
@@ -1401,9 +1460,9 @@
}
// Ensure that we have the appropriate request data.
if (!options.data && model && (method === 'create' || method === 'update')) {
if (options.data == null && model && (method === 'create' || method === 'update')) {
params.contentType = 'application/json';
params.data = JSON.stringify(model.toJSON(options));
params.data = JSON.stringify(options.attrs || model.toJSON(options));
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
@@ -1442,7 +1501,9 @@
};
// Make the request, allowing the user to override any Ajax options.
return Backbone.ajax(_.extend(params, options));
var xhr = Backbone.ajax(_.extend(params, options));
model.trigger('request', model, xhr, options);
return xhr;
};
// Set the default implementation of `Backbone.ajax` to proxy through to `$`.

View File

@@ -18,17 +18,21 @@ $(document).ready(function() {
}));
test("new and sort", 7, function() {
test("new and sort", 9, function() {
var counter = 0;
col.on('sort', function(){ counter++; });
equal(col.first(), a, "a should be first");
equal(col.last(), d, "d should be last");
col.comparator = function(a, b) {
return a.id > b.id ? -1 : 1;
};
col.sort();
equal(counter, 1);
equal(col.first(), a, "a should be first");
equal(col.last(), d, "d should be last");
col.comparator = function(model) { return model.id; };
col.sort();
equal(counter, 2);
equal(col.first(), d, "d should be first");
equal(col.last(), a, "a should be last");
equal(col.length, 4);
@@ -58,10 +62,10 @@ $(document).ready(function() {
strictEqual(collection.last().get('a'), 4);
});
test("get, getByCid", 3, function() {
test("get", 3, function() {
equal(col.get(0), d);
equal(col.get(2), b);
equal(col.getByCid(col.first().cid), col.first());
equal(col.get(col.first().cid), col.first());
});
test("get with non-default ids", 2, function() {
@@ -304,13 +308,13 @@ $(document).ready(function() {
var colE = new Backbone.Collection([e]);
var colF = new Backbone.Collection([f]);
ok(e != f);
ok(colE.length == 1);
ok(colF.length == 1);
ok(colE.length === 1);
ok(colF.length === 1);
colE.remove(e);
equal(passed, false);
ok(colE.length == 0);
ok(colE.length === 0);
colF.remove(e);
ok(colF.length == 0);
ok(colF.length === 0);
equal(passed, true);
});
@@ -338,13 +342,13 @@ $(document).ready(function() {
});
equal(colE, e.collection);
colF.remove(e);
ok(colF.length == 0);
ok(colE.length == 1);
ok(colF.length === 0);
ok(colE.length === 1);
equal(counter, 1);
equal(colE, e.collection);
colE.remove(e);
equal(null, e.collection);
ok(colE.length == 0);
ok(colE.length === 0);
equal(counter, 2);
});
@@ -354,8 +358,8 @@ $(document).ready(function() {
var colE = new Backbone.Collection([e]);
var colF = new Backbone.Collection([e]);
e.destroy();
ok(colE.length == 0);
ok(colF.length == 0);
ok(colE.length === 0);
ok(colF.length === 0);
equal(undefined, e.collection);
});
@@ -365,8 +369,8 @@ $(document).ready(function() {
var colE = new Backbone.Collection([e]);
var colF = new Backbone.Collection([e]);
e.destroy();
ok(colE.length == 0);
ok(colF.length == 0);
ok(colE.length === 0);
ok(colF.length === 0);
equal(undefined, e.collection);
});
@@ -542,7 +546,7 @@ $(document).ready(function() {
equal(col.length, 0);
});
test("#861, adding models to a collection which do not pass validation", 2, function() {
test("#861, adding models to a collection which do not pass validation", function() {
var Model = Backbone.Model.extend({
validate: function(attrs) {
if (attrs.id == 3) return "id can't be 3";
@@ -567,9 +571,9 @@ $(document).ready(function() {
validate: function(attrs){ if (!attrs.valid) return 'invalid'; }
});
var model = new collection.model({id: 1, valid: true});
collection.add([model, {id: 2}]);;
collection.add([model, {id: 2}]);
model.trigger('test');
ok(collection.getByCid(model.cid));
ok(collection.get(model.cid));
ok(collection.get(1));
ok(!collection.get(2));
equal(collection.length, 1);
@@ -726,4 +730,102 @@ $(document).ready(function() {
c.add({id: 4});
});
test("#1407 parse option on constructor 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(model, {parse:true});
equal(c.length, 2);
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])
.on('reset', function(collection, options) {
deepEqual(options.previousModels, [model]);
});
collection.reset([]);
});
test("update", function() {
var m1 = new Backbone.Model();
var m2 = new Backbone.Model({id: 2});
var m3 = new Backbone.Model();
var c = new Backbone.Collection([m1, m2]);
// Test add/change/remove events
c.on('add', function(model) {
strictEqual(model, m3);
});
c.on('change', function(model) {
strictEqual(model, m2);
});
c.on('remove', function(model) {
strictEqual(model, m1);
});
// remove: false doesn't remove any models
c.update([], {remove: false});
strictEqual(c.length, 2);
// add: false doesn't add any models
c.update([m1, m2, m3], {add: false});
strictEqual(c.length, 2);
// merge: false doesn't change any models
c.update([m1, {id: 2, a: 1}], {merge: false, remove: true});
strictEqual(m2.get('a'), void 0);
// add: false, remove: false only merges existing models
c.update([m1, {id: 2, a: 0}, m3, {id: 4}], {add: false, remove: false});
strictEqual(c.length, 2);
strictEqual(m2.get('a'), 0);
// default options add/remove/merge as appropriate
c.update([{id: 2, a: 1}, m3]);
strictEqual(c.length, 3);
strictEqual(m2.get('a'), 1);
// Test removing models not passing an argument
c.off('remove');
c.update([], {remove: true});
strictEqual(c.length, 0);
});
});

View File

@@ -17,7 +17,7 @@ $(document).ready(function() {
test("binding and triggering multiple events", 4, function() {
var obj = { counter: 0 };
_.extend(obj,Backbone.Events);
_.extend(obj, Backbone.Events);
obj.on('a b c', function() { obj.counter += 1; });
@@ -35,6 +35,38 @@ $(document).ready(function() {
equal(obj.counter, 5);
});
test("binding and triggering with event maps", function() {
var obj = { counter: 0 };
_.extend(obj, Backbone.Events);
var increment = function() {
this.counter += 1;
};
obj.on({
a: increment,
b: increment,
c: increment
}, obj);
obj.trigger('a');
equal(obj.counter, 1);
obj.trigger('a b');
equal(obj.counter, 3);
obj.trigger('c');
equal(obj.counter, 4);
obj.off({
a: increment,
c: increment
}, obj);
obj.trigger('a b c');
equal(obj.counter, 5);
});
test("trigger all for each event", 3, function() {
var a, b, obj = { counter: 0 };
_.extend(obj, Backbone.Events);
@@ -192,4 +224,90 @@ $(document).ready(function() {
obj.trigger('event');
});
test("once", 2, function() {
// Same as the previous test, but we use once rather than having to explicitly unbind
var obj = { counterA: 0, counterB: 0 };
_.extend(obj,Backbone.Events);
var incrA = function(){ obj.counterA += 1; obj.trigger('event'); };
var incrB = function(){ obj.counterB += 1 };
obj.once('event', incrA);
obj.once('event', incrB);
obj.trigger('event');
obj.trigger('event');
obj.trigger('event');
equal(obj.counterA, 1, 'counterA should have only been incremented once.');
equal(obj.counterB, 1, 'counterB should have only been incremented once.');
});
test("once variant one", 3, function() {
var f = function(){ ok(true); };
var a = _.extend({}, Backbone.Events).once('event', f);
var b = _.extend({}, Backbone.Events).on('event', f);
a.trigger('event');
b.trigger('event');
b.trigger('event');
});
test("once variant two", 3, function() {
var f = function(){ ok(true); };
var obj = _.extend({}, Backbone.Events);
obj
.once('event', f)
.on('event', f)
.trigger('event')
.trigger('event');
});
test("once with off", 0, function() {
var f = function(){ ok(true); };
var obj = _.extend({}, Backbone.Events);
obj.once('event', f);
obj.off('event', f);
obj.trigger('event');
});
test("once with event maps", function() {
var obj = { counter: 0 };
_.extend(obj, Backbone.Events);
var increment = function() {
this.counter += 1;
};
obj.once({
a: increment,
b: increment,
c: increment
}, obj);
obj.trigger('a');
equal(obj.counter, 1);
obj.trigger('a b');
equal(obj.counter, 2);
obj.trigger('c');
equal(obj.counter, 3);
obj.trigger('a b c');
equal(obj.counter, 3);
});
test("Backbone object inherits Events", function() {
ok(Backbone.on === Backbone.Events.on);
});
asyncTest("once with asynchronous events", 1, function() {
var func = _.debounce(function() { ok(true); start(); }, 50);
var obj = _.extend({}, Backbone.Events).once('async', func);
obj.trigger('async');
obj.trigger('async');
});
});

View File

@@ -270,7 +270,7 @@ $(document).ready(function() {
};
}
});
var model = new Defaulted({two: null});
model = new Defaulted({two: null});
equal(model.get('one'), 3);
equal(model.get('two'), null);
});
@@ -351,7 +351,7 @@ $(document).ready(function() {
equal(lastError, "Can't change admin status.");
});
test("isValid", 5, function() {
test("isValid", function() {
var model = new Backbone.Model({valid: true});
model.validate = function(attrs) {
if (!attrs.valid) return "invalid";
@@ -359,8 +359,7 @@ $(document).ready(function() {
equal(model.isValid(), true);
equal(model.set({valid: false}), false);
equal(model.isValid(), true);
ok(model.set('valid', false, {silent: true}));
equal(model.isValid(), false);
ok(!model.set('valid', false, {silent: true}));
});
test("save", 2, function() {
@@ -369,6 +368,19 @@ $(document).ready(function() {
ok(_.isEqual(this.syncArgs.model, doc));
});
test("save with PATCH", function() {
doc.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4});
doc.save();
equal(this.syncArgs.method, 'update');
equal(this.syncArgs.options.attrs, undefined);
doc.save({b: 2, d: 4}, {patch: true});
equal(this.syncArgs.method, 'patch');
equal(_.size(this.syncArgs.options.attrs), 2);
equal(this.syncArgs.options.attrs.d, 4);
equal(this.syncArgs.options.attrs.a, undefined);
});
test("save in positional style", 1, function() {
var model = new Backbone.Model();
model.sync = function(method, model, options) {
@@ -402,7 +414,7 @@ $(document).ready(function() {
ok(true, "non-persisted model should not call sync");
});
test("validate", 7, function() {
test("validate", function() {
var lastError;
var model = new Backbone.Model();
model.validate = function(attrs) {
@@ -415,8 +427,6 @@ $(document).ready(function() {
equal(result, model);
equal(model.get('a'), 100);
equal(lastError, undefined);
result = model.set({admin: true}, {silent: true});
equal(model.get('admin'), true);
result = model.set({a: 200, admin: false});
equal(lastError, "Can't change admin status.");
equal(result, false);
@@ -639,7 +649,7 @@ $(document).ready(function() {
equal(model.get('x'), 3);
});
test("save with wait validates attributes", 1, function() {
test("save with wait validates attributes", function() {
var model = new Backbone.Model();
model.url = '/test';
model.validate = function() { ok(true); };

View File

@@ -107,7 +107,7 @@ $(document).ready(function() {
},
optionalItem: function(arg){
this.arg = arg !== undefined ? arg : null;
this.arg = arg !== void 0 ? arg : null;
},
splat : function(args) {
@@ -139,7 +139,7 @@ $(document).ready(function() {
location.replace('http://example.com#search/news');
Backbone.history.checkUrl();
equal(router.query, 'news');
equal(router.page, undefined);
equal(router.page, void 0);
equal(lastRoute, 'search');
equal(lastArgs[0], 'news');
});
@@ -265,12 +265,12 @@ $(document).ready(function() {
if (!Backbone.history.iframe) ok(true);
});
test("route callback gets passed decoded values", 3, function() {
test("#967 - Route callback gets passed encoded values.", 3, function() {
var route = 'has%2Fslash/complex-has%23hash/has%20space';
Backbone.history.navigate(route, {trigger: true});
equal(router.first, 'has/slash');
equal(router.part, 'has#hash');
equal(router.rest, 'has space');
strictEqual(router.first, 'has%2Fslash');
strictEqual(router.part, 'has%23hash');
strictEqual(router.rest, 'has%20space');
});
test("correctly handles URLs with % (#868)", 3, function() {
@@ -279,7 +279,7 @@ $(document).ready(function() {
location.replace('http://example.com#search/fat');
Backbone.history.checkUrl();
equal(router.query, 'fat');
equal(router.page, undefined);
equal(router.page, void 0);
equal(lastRoute, 'search');
});