Update vendors.

Former-commit-id: 94bb6b8541c223d3ef6eb8aad5fb5925f2d3be48
This commit is contained in:
John-David Dalton
2012-10-03 23:21:39 -07:00
parent a210377f35
commit 25ba18e570
6 changed files with 178 additions and 60 deletions

View File

@@ -183,22 +183,22 @@
// is automatically generated and assigned for you. // is automatically generated and assigned for you.
var Model = Backbone.Model = function(attributes, options) { var Model = Backbone.Model = function(attributes, options) {
var defaults; var defaults;
attributes || (attributes = {}); var attrs = attributes || {};
if (options && options.collection) this.collection = options.collection; if (options && options.collection) this.collection = options.collection;
if (options && options.parse) attributes = this.parse(attributes); if (options && options.parse) attributes = this.parse(attributes);
if (defaults = _.result(this, 'defaults')) { if (defaults = _.result(this, 'defaults')) {
attributes = _.extend({}, defaults, attributes); attrs = _.extend({}, defaults, attrs);
} }
this.attributes = {}; this.attributes = {};
this._escapedAttributes = {}; this._escapedAttributes = {};
this.cid = _.uniqueId('c'); this.cid = _.uniqueId('c');
this.changed = {}; this.changed = {};
this._silent = {}; this._changes = {};
this._pending = {}; this._pending = {};
this.set(attributes, {silent: true}); this.set(attrs, {silent: true});
// Reset change tracking. // Reset change tracking.
this.changed = {}; this.changed = {};
this._silent = {}; this._changes = {};
this._pending = {}; this._pending = {};
this._previousAttributes = _.clone(this.attributes); this._previousAttributes = _.clone(this.attributes);
this.initialize.apply(this, arguments); this.initialize.apply(this, arguments);
@@ -210,14 +210,18 @@
// A hash of attributes whose current and previous value differ. // A hash of attributes whose current and previous value differ.
changed: null, changed: null,
// A hash of attributes that have silently changed since the last time // A hash of attributes that have changed since the last time `change`
// `change` was called. Will become pending attributes on the next call. // was called.
_silent: null, _changes: null,
// A hash of attributes that have changed since the last `'change'` event // A hash of attributes that have changed since the last `change` event
// began. // began.
_pending: null, _pending: null,
// A hash of attributes with the current model state to determine if
// a `change` should be recorded within a nested `change` block.
_changing : null,
// The default name for the JSON `id` attribute is `"id"`. MongoDB and // The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`. // CouchDB users may want to set this to `"_id"`.
idAttribute: 'id', idAttribute: 'id',
@@ -257,23 +261,22 @@
// Set a hash of model attributes on the object, firing `"change"` unless // Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it. // you choose to silence it.
set: function(key, value, options) { set: function(attrs, options) {
var attrs, attr, val; var attr, key, val;
if (attrs == null) return this;
// Handle both `"key", value` and `{key: value}` -style arguments. // Handle both `"key", value` and `{key: value}` -style arguments.
if (_.isObject(key) || key == null) { if (!_.isObject(attrs)) {
attrs = key; key = attrs;
options = value; (attrs = {})[key] = options;
} else { options = arguments[2];
attrs = {};
attrs[key] = value;
} }
// Extract attributes and options. // Extract attributes and options.
options || (options = {}); var silent = options && options.silent;
if (!attrs) return this; var unset = options && options.unset;
if (attrs instanceof Model) attrs = attrs.attributes; if (attrs instanceof Model) attrs = attrs.attributes;
if (options.unset) for (attr in attrs) attrs[attr] = void 0; if (unset) for (attr in attrs) attrs[attr] = void 0;
// Run validation. // Run validation.
if (!this._validate(attrs, options)) return false; if (!this._validate(attrs, options)) return false;
@@ -281,7 +284,7 @@
// Check for changes of `id`. // Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
var changes = options.changes = {}; var changing = this._changing;
var now = this.attributes; var now = this.attributes;
var escaped = this._escapedAttributes; var escaped = this._escapedAttributes;
var prev = this._previousAttributes || {}; var prev = this._previousAttributes || {};
@@ -291,27 +294,30 @@
val = attrs[attr]; val = attrs[attr];
// If the new and current value differ, record the change. // If the new and current value differ, record the change.
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) { if (!_.isEqual(now[attr], val) || (unset && _.has(now, attr))) {
delete escaped[attr]; delete escaped[attr];
(options.silent ? this._silent : changes)[attr] = true; this._changes[attr] = true;
} }
// Update or delete the current value. // Update or delete the current value.
options.unset ? delete now[attr] : now[attr] = val; unset ? delete now[attr] : now[attr] = val;
// If the new and previous value differ, record the change. If not, // If the new and previous value differ, record the change. If not,
// then remove changes for this attribute. // then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) { if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) {
this.changed[attr] = val; this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true; if (!silent) this._pending[attr] = true;
} else { } else {
delete this.changed[attr]; delete this.changed[attr];
delete this._pending[attr]; delete this._pending[attr];
if (!changing) delete this._changes[attr];
} }
if (changing && _.isEqual(now[attr], changing[attr])) delete this._changes[attr];
} }
// Fire the `"change"` events. // Fire the `"change"` events.
if (!options.silent) this.change(options); if (!silent) this.change(options);
return this; return this;
}, },
@@ -346,16 +352,14 @@
// Set a hash of model attributes, and sync the model to the server. // Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's // If the server returns an attributes hash that differs, the model's
// state will be `set` again. // state will be `set` again.
save: function(key, value, options) { save: function(attrs, options) {
var attrs, current, done; var key, current, done;
// Handle both `("key", value)` and `({key: value})` -style calls. // Handle both `"key", value` and `{key: value}` -style arguments.
if (_.isObject(key) || key == null) { if (attrs != null && !_.isObject(attrs)) {
attrs = key; key = attrs;
options = value; (attrs = {})[key] = options;
} else { options = arguments[2];
attrs = {};
attrs[key] = value;
} }
options = options ? _.clone(options) : {}; options = options ? _.clone(options) : {};
@@ -372,7 +376,7 @@
} }
// Do not persist invalid models. // Do not persist invalid models.
if (!attrs && !this.isValid()) return false; if (!attrs && !this._validate(null, options)) return false;
// After a successful server-side save, the client is (optionally) // After a successful server-side save, the client is (optionally)
// updated with the server-side state. // updated with the server-side state.
@@ -455,18 +459,25 @@
// a `"change:attribute"` event for each changed attribute. // a `"change:attribute"` event for each changed attribute.
// Calling this will cause all objects observing the model to update. // Calling this will cause all objects observing the model to update.
change: function(options) { change: function(options) {
options || (options = {});
var changing = this._changing; var changing = this._changing;
this._changing = true; var current = this._changing = {};
// Silent changes become pending changes. // Silent changes become pending changes.
for (var attr in this._silent) this._pending[attr] = true; for (var attr in this._changes) this._pending[attr] = true;
// Silent changes are triggered. // Trigger 'change:attr' for any new or silent changes.
var changes = _.extend({}, options.changes, this._silent); var changes = this._changes;
this._silent = {}; this._changes = {};
// Set the correct state for this._changing values
var triggers = [];
for (var attr in changes) { for (var attr in changes) {
this.trigger('change:' + attr, this, this.get(attr), options); current[attr] = this.get(attr);
triggers.push(attr);
}
for (var i=0, l=triggers.length; i < l; i++) {
this.trigger('change:' + triggers[i], this, current[triggers[i]], options);
} }
if (changing) return this; if (changing) return this;
@@ -476,13 +487,13 @@
this.trigger('change', this, options); this.trigger('change', this, options);
// Pending and silent changes still remain. // Pending and silent changes still remain.
for (var attr in this.changed) { for (var attr in this.changed) {
if (this._pending[attr] || this._silent[attr]) continue; if (this._pending[attr] || this._changes[attr]) continue;
delete this.changed[attr]; delete this.changed[attr];
} }
this._previousAttributes = _.clone(this.attributes); this._previousAttributes = _.clone(this.attributes);
} }
this._changing = false; this._changing = null;
return this; return this;
}, },
@@ -532,7 +543,7 @@
// returning `true` if all is well. If a specific `error` callback has // returning `true` if all is well. If a specific `error` callback has
// been passed, call that instead of firing the general `"error"` event. // been passed, call that instead of firing the general `"error"` event.
_validate: function(attrs, options) { _validate: function(attrs, options) {
if (options.silent || !this.validate) return true; if (options && options.silent || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs); attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options); var error = this.validate(attrs, options);
if (!error) return true; if (!error) return true;
@@ -894,9 +905,10 @@
// Cached regular expressions for matching named param parts and splatted // Cached regular expressions for matching named param parts and splatted
// parts of route strings. // parts of route strings.
var optionalParam = /\((.*?)\)/g;
var namedParam = /:\w+/g; var namedParam = /:\w+/g;
var splatParam = /\*\w+/g; var splatParam = /\*\w+/g;
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g; var escapeRegExp = /[-{}[\]+?.,\\^$|#\s]/g;
// Set up all inheritable **Backbone.Router** properties and methods. // Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Router.prototype, Events, { _.extend(Router.prototype, Events, {
@@ -947,6 +959,7 @@
// against the current location hash. // against the current location hash.
_routeToRegExp: function(route) { _routeToRegExp: function(route) {
route = route.replace(escapeRegExp, '\\$&') route = route.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, '([^\/]+)') .replace(namedParam, '([^\/]+)')
.replace(splatParam, '(.*?)'); .replace(splatParam, '(.*?)');
return new RegExp('^' + route + '$'); return new RegExp('^' + route + '$');
@@ -1059,7 +1072,7 @@
// opened by a non-pushState browser. // opened by a non-pushState browser.
this.fragment = fragment; this.fragment = fragment;
var loc = this.location; var loc = this.location;
var atRoot = (loc.pathname.replace(/[^/]$/, '$&/') === this.root) && !loc.search; var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
// If we've started off with a route from a `pushState`-enabled browser, // 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... // but we're currently in a browser that doesn't support it...
@@ -1073,7 +1086,7 @@
// in a browser where it could be `pushState`-based instead... // in a browser where it could be `pushState`-based instead...
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) { } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = this.getHash().replace(routeStripper, ''); this.fragment = this.getHash().replace(routeStripper, '');
this.history.replaceState({}, document.title, this.root + this.fragment); this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
} }
if (!this.options.silent) return this.loadUrl(); if (!this.options.silent) return this.loadUrl();
@@ -1320,7 +1333,7 @@
if (this.className) attrs['class'] = _.result(this, 'className'); if (this.className) attrs['class'] = _.result(this, 'className');
this.setElement(this.make(_.result(this, 'tagName'), attrs), false); this.setElement(this.make(_.result(this, 'tagName'), attrs), false);
} else { } else {
this.setElement(this.el, false); this.setElement(_.result(this, 'el'), false);
} }
} }
@@ -1435,9 +1448,12 @@
child = function(){ parent.apply(this, arguments); }; child = function(){ parent.apply(this, arguments); };
} }
// Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps);
// Set the prototype chain to inherit from `parent`, without calling // Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function. // `parent`'s constructor function.
function Surrogate(){ this.constructor = child; }; var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype; Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate; child.prototype = new Surrogate;
@@ -1445,9 +1461,6 @@
// if supplied. // if supplied.
if (protoProps) _.extend(child.prototype, protoProps); if (protoProps) _.extend(child.prototype, protoProps);
// Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps);
// Set a convenience property in case the parent's prototype is needed // Set a convenience property in case the parent's prototype is needed
// later. // later.
child.__super__ = parent.prototype; child.__super__ = parent.prototype;

View File

@@ -740,9 +740,9 @@ $(document).ready(function() {
model.set({b: 2}, {silent: true}); model.set({b: 2}, {silent: true});
}); });
model.set({b: 0}); model.set({b: 0});
deepEqual(changes, [0, 1, 1]); deepEqual(changes, [0, 1]);
model.change(); model.change();
deepEqual(changes, [0, 1, 1, 2, 1]); deepEqual(changes, [0, 1, 2, 1]);
}); });
test("nested set multiple times", 1, function() { test("nested set multiple times", 1, function() {
@@ -816,4 +816,66 @@ $(document).ready(function() {
strictEqual(model.save(), false); strictEqual(model.save(), false);
}); });
test("#1377 - Save without attrs triggers 'error'.", 1, function() {
var Model = Backbone.Model.extend({
url: '/test/',
sync: function(method, model, options){ options.success(); },
validate: function(){ return 'invalid'; }
});
var model = new Model({id: 1});
model.on('error', function(){ ok(true); });
model.save();
});
test("#1545 - `undefined` can be passed to a model constructor without coersion", function() {
var Model = Backbone.Model.extend({
defaults: { one: 1 },
initialize : function(attrs, opts) {
equal(attrs, undefined);
}
});
var emptyattrs = new Model();
var undefinedattrs = new Model(undefined);
});
asyncTest("#1478 - Model `save` does not trigger change on unchanged attributes", 0, function() {
var Model = Backbone.Model.extend({
sync: function(method, model, options) {
setTimeout(function(){
options.success();
start();
}, 0);
}
});
new Model({x: true})
.on('change:x', function(){ ok(false); })
.save(null, {wait: true});
});
test("#1664 - Changing from one value, silently to another, back to original does not trigger change.", 0, function() {
var model = new Backbone.Model({x:1});
model.on('change:x', function() { ok(false); });
model.set({x:2},{silent:true});
model.set({x:3},{silent:true});
model.set({x:1});
});
test("#1664 - multiple silent changes nested inside a change event", 2, function() {
var changes = [];
var model = new Backbone.Model();
model.on('change', function() {
model.set({a:'c'}, {silent:true});
model.set({b:2}, {silent:true});
model.unset('c', {silent:true});
model.set({a:'a'}, {silent:true});
model.set({b:1}, {silent:true});
model.set({c:'item'}, {silent:true});
});
model.on('change:a change:b change:c', function(model, val) { changes.push(val); });
model.set({a:'a', b:1, c:'item'});
deepEqual(changes, ['a',1,'item']);
model.change();
deepEqual(changes, ['a',1,'item']);
});
}); });

View File

@@ -69,6 +69,7 @@ $(document).ready(function() {
"contacts": "contacts", "contacts": "contacts",
"contacts/new": "newContact", "contacts/new": "newContact",
"contacts/:id": "loadContact", "contacts/:id": "loadContact",
"optional(/:item)": "optionalItem",
"splat/*args/end": "splat", "splat/*args/end": "splat",
"*first/complex-:part/*rest": "complex", "*first/complex-:part/*rest": "complex",
":entity?*args": "query", ":entity?*args": "query",
@@ -105,6 +106,10 @@ $(document).ready(function() {
this.contact = 'load'; this.contact = 'load';
}, },
optionalItem: function(arg){
this.arg = arg !== undefined ? arg : null;
},
splat : function(args) { splat : function(args) {
this.args = args; this.args = args;
}, },
@@ -199,6 +204,15 @@ $(document).ready(function() {
equal(router.args, 'long-list/of/splatted_99args'); equal(router.args, 'long-list/of/splatted_99args');
}); });
test("routes (optional)", 2, function() {
location.replace('http://example.com#optional');
Backbone.history.checkUrl();
equal(router.arg, null);
location.replace('http://example.com#optional/thing');
Backbone.history.checkUrl();
equal(router.arg, 'thing');
});
test("routes (complex)", 3, function() { test("routes (complex)", 3, function() {
location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven'); location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
Backbone.history.checkUrl(); Backbone.history.checkUrl();
@@ -449,4 +463,22 @@ $(document).ready(function() {
}); });
}); });
test("#1695 - hashChange to pushState with search.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root?a=b#x/y');
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(){},
replaceState: function(state, title, url){
strictEqual(url, '/root/x/y?a=b');
}
}
});
Backbone.history.start({
root: 'root',
pushState: true
});
});
}); });

View File

@@ -312,4 +312,15 @@ $(document).ready(function() {
view.remove(); view.remove();
}); });
test("Provide function for el.", 1, function() {
var View = Backbone.View.extend({
el: function() {
return "<p><a></a></p>";
}
});
var view = new View;
ok(view.$el.is('p:has(a)'));
});
}); });

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
// Underscore.js 1.4.0 // Underscore.js 1.4.1
// http://underscorejs.org // http://underscorejs.org
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore may be freely distributed under the MIT license. // Underscore may be freely distributed under the MIT license.
@@ -65,7 +65,7 @@
} }
// Current version. // Current version.
_.VERSION = '1.4.0'; _.VERSION = '1.4.1';
// Collection Functions // Collection Functions
// -------------------- // --------------------