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.
var Model = Backbone.Model = function(attributes, options) {
var defaults;
attributes || (attributes = {});
var attrs = attributes || {};
if (options && options.collection) this.collection = options.collection;
if (options && options.parse) attributes = this.parse(attributes);
if (defaults = _.result(this, 'defaults')) {
attributes = _.extend({}, defaults, attributes);
attrs = _.extend({}, defaults, attrs);
}
this.attributes = {};
this._escapedAttributes = {};
this.cid = _.uniqueId('c');
this.changed = {};
this._silent = {};
this._changes = {};
this._pending = {};
this.set(attributes, {silent: true});
this.set(attrs, {silent: true});
// Reset change tracking.
this.changed = {};
this._silent = {};
this._changes = {};
this._pending = {};
this._previousAttributes = _.clone(this.attributes);
this.initialize.apply(this, arguments);
@@ -210,14 +210,18 @@
// A hash of attributes whose current and previous value differ.
changed: null,
// A hash of attributes that have silently changed since the last time
// `change` was called. Will become pending attributes on the next call.
_silent: null,
// A hash of attributes that have changed since the last time `change`
// was called.
_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.
_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
// CouchDB users may want to set this to `"_id"`.
idAttribute: 'id',
@@ -257,23 +261,22 @@
// Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it.
set: function(key, value, options) {
var attrs, attr, val;
set: function(attrs, options) {
var attr, key, val;
if (attrs == null) return this;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
if (!_.isObject(attrs)) {
key = attrs;
(attrs = {})[key] = options;
options = arguments[2];
}
// Extract attributes and options.
options || (options = {});
if (!attrs) return this;
var silent = options && options.silent;
var unset = options && options.unset;
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.
if (!this._validate(attrs, options)) return false;
@@ -281,7 +284,7 @@
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
var changes = options.changes = {};
var changing = this._changing;
var now = this.attributes;
var escaped = this._escapedAttributes;
var prev = this._previousAttributes || {};
@@ -291,27 +294,30 @@
val = attrs[attr];
// 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];
(options.silent ? this._silent : changes)[attr] = true;
this._changes[attr] = true;
}
// 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,
// then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) {
this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true;
if (!silent) this._pending[attr] = true;
} else {
delete this.changed[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.
if (!options.silent) this.change(options);
if (!silent) this.change(options);
return this;
},
@@ -346,16 +352,14 @@
// 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
// state will be `set` again.
save: function(key, value, options) {
var attrs, current, done;
save: function(attrs, options) {
var key, current, done;
// Handle both `("key", value)` and `({key: value})` -style calls.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (attrs != null && !_.isObject(attrs)) {
key = attrs;
(attrs = {})[key] = options;
options = arguments[2];
}
options = options ? _.clone(options) : {};
@@ -372,7 +376,7 @@
}
// 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)
// updated with the server-side state.
@@ -455,18 +459,25 @@
// a `"change:attribute"` event for each changed attribute.
// Calling this will cause all objects observing the model to update.
change: function(options) {
options || (options = {});
var changing = this._changing;
this._changing = true;
var current = this._changing = {};
// 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.
var changes = _.extend({}, options.changes, this._silent);
this._silent = {};
// Trigger 'change:attr' for any new or silent changes.
var changes = this._changes;
this._changes = {};
// Set the correct state for this._changing values
var triggers = [];
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;
@@ -476,13 +487,13 @@
this.trigger('change', this, options);
// Pending and silent changes still remain.
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];
}
this._previousAttributes = _.clone(this.attributes);
}
this._changing = false;
this._changing = null;
return this;
},
@@ -532,7 +543,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.silent || !this.validate) return true;
if (options && options.silent || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options);
if (!error) return true;
@@ -894,9 +905,10 @@
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
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, {
@@ -947,6 +959,7 @@
// against the current location hash.
_routeToRegExp: function(route) {
route = route.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, '([^\/]+)')
.replace(splatParam, '(.*?)');
return new RegExp('^' + route + '$');
@@ -1059,7 +1072,7 @@
// opened by a non-pushState browser.
this.fragment = fragment;
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,
// 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...
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
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();
@@ -1320,7 +1333,7 @@
if (this.className) attrs['class'] = _.result(this, 'className');
this.setElement(this.make(_.result(this, 'tagName'), attrs), false);
} else {
this.setElement(this.el, false);
this.setElement(_.result(this, 'el'), false);
}
}
@@ -1435,9 +1448,12 @@
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
// `parent`'s constructor function.
function Surrogate(){ this.constructor = child; };
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;
@@ -1445,9 +1461,6 @@
// if supplied.
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
// later.
child.__super__ = parent.prototype;

View File

@@ -740,9 +740,9 @@ $(document).ready(function() {
model.set({b: 2}, {silent: true});
});
model.set({b: 0});
deepEqual(changes, [0, 1, 1]);
deepEqual(changes, [0, 1]);
model.change();
deepEqual(changes, [0, 1, 1, 2, 1]);
deepEqual(changes, [0, 1, 2, 1]);
});
test("nested set multiple times", 1, function() {
@@ -816,4 +816,66 @@ $(document).ready(function() {
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/new": "newContact",
"contacts/:id": "loadContact",
"optional(/:item)": "optionalItem",
"splat/*args/end": "splat",
"*first/complex-:part/*rest": "complex",
":entity?*args": "query",
@@ -105,6 +106,10 @@ $(document).ready(function() {
this.contact = 'load';
},
optionalItem: function(arg){
this.arg = arg !== undefined ? arg : null;
},
splat : function(args) {
this.args = args;
},
@@ -199,6 +204,15 @@ $(document).ready(function() {
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() {
location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
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();
});
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
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore may be freely distributed under the MIT license.
@@ -65,7 +65,7 @@
}
// Current version.
_.VERSION = '1.4.0';
_.VERSION = '1.4.1';
// Collection Functions
// --------------------