Test
@@ -29,9 +29,23 @@
Test
);
+
+
diff --git a/test/test-build.js b/test/test-build.js
index 18a475a38a..d59674686e 100644
--- a/test/test-build.js
+++ b/test/test-build.js
@@ -54,9 +54,9 @@
};
/** List of all Lo-Dash methods */
- var allMethods = _.functions(_).filter(function(methodName) {
- return !/^_/.test(methodName);
- });
+ var allMethods = _.functions(_)
+ .filter(function(methodName) { return !/^_/.test(methodName); })
+ .concat('chain');
/** List of "Arrays" category methods */
var arraysMethods = [
@@ -86,7 +86,6 @@
/** List of "Chaining" category methods */
var chainingMethods = [
- 'chain',
'mixin',
'tap',
'value'
@@ -148,6 +147,7 @@
var objectsMethods = [
'assign',
'clone',
+ 'cloneDeep',
'defaults',
'extend',
'forIn',
@@ -229,6 +229,7 @@
'max',
'min',
'mixin',
+ 'once',
'pick',
'reduce',
'reduceRight',
@@ -249,6 +250,7 @@
/** List of methods used by Underscore */
var underscoreMethods = _.without.apply(_, [allMethods].concat([
'bindKey',
+ 'cloneDeep',
'forIn',
'forOwn',
'isPlainObject',
@@ -445,7 +447,7 @@
console.log(e);
pass = false;
}
- equal(pass, true, '_.' + methodName + ': ' + message);
+ ok(pass, '_.' + methodName + ': ' + message);
}
/*--------------------------------------------------------------------------*/
@@ -489,61 +491,69 @@
context._ = _;
vm.runInContext(source, context);
- var templates = context._.templates;
- equal(templates.a(data.a).replace(/[\r\n]+/g, ''), '
', basename);
- equal(templates.b(data.b), 'Hello stooge.', basename);
- equal(templates.c(data.c), 'Hello ES6!', basename);
+ equal(_.templates.a(data.a).replace(/[\r\n]+/g, ''), '
', basename);
+ equal(_.templates.b(data.b), 'Hello stooge.', basename);
+ equal(_.templates.c(data.c), 'Hello ES6!', basename);
delete _.templates;
start();
});
});
- ['', 'moduleId=underscore'].forEach(function(command) {
+ var commands = [
+ '',
+ 'moduleId=underscore'
+ ];
+
+ commands.forEach(function(command) {
asyncTest('`lodash template=*.jst` exports=amd' + (command ? ' ' + command : ''), function() {
var start = _.after(2, _.once(QUnit.start));
build(['-s', 'template=' + templatePath + '/*.jst', 'exports=amd'].concat(command || []), function(source, filePath) {
var moduleId,
basename = path.basename(filePath, '.js'),
- context = createContext(),
- pass = false;
+ context = createContext();
- (context.define = function(requires, factory) {
+ context.define = function(requires, factory) {
factory(_);
- var templates = _.templates;
- moduleId = requires + '';
- pass = 'a' in templates && 'b' in templates;
- })
- .amd = {};
+ moduleId = requires[0];
+ };
+ context.define.amd = {};
vm.runInContext(source, context);
- equal(moduleId, command ? 'underscore' : 'lodash');
- ok(pass, basename);
+ equal(moduleId, (command ? 'underscore' : 'lodash'), basename);
+ ok('a' in _.templates && 'b' in _.templates, basename);
delete _.templates;
start();
});
});
- });
- asyncTest('`lodash settings=...`', function() {
- var start = _.after(2, _.once(QUnit.start));
+ asyncTest('`lodash settings=...`' + (command ? ' ' + command : ''), function() {
+ var start = _.after(2, _.once(QUnit.start));
- build(['-s', 'template=' + templatePath + '/*.tpl', 'settings={interpolate:/\\{\\{([\\s\\S]+?)\\}\\}/}'], function(source, filePath) {
- var basename = path.basename(filePath, '.js'),
- context = createContext();
+ build(['-s', 'template=' + templatePath + '/*.tpl', 'settings={interpolate:/\\{\\{([\\s\\S]+?)\\}\\}/}'].concat(command || []), function(source, filePath) {
+ var moduleId,
+ basename = path.basename(filePath, '.js'),
+ context = createContext();
- var data = {
- 'd': { 'name': 'Mustache' }
- };
+ var data = {
+ 'd': { 'name': 'Mustache' }
+ };
- context._ = _;
- vm.runInContext(source, context);
- var templates = context._.templates;
+ context.define = function(requires, factory) {
+ factory(_);
+ moduleId = requires[0];
+ };
- equal(templates.d(data.d), 'Hello Mustache!', basename);
- start();
+ context.define.amd = {};
+ vm.runInContext(source, context);
+
+ equal(moduleId, (command ? 'underscore' : 'lodash'), basename);
+ equal(_.templates.d(data.d), 'Hello Mustache!', basename);
+ delete _.templates;
+ start();
+ });
});
});
}());
@@ -553,6 +563,9 @@
QUnit.module('independent builds');
(function() {
+ var reComment = /\/\*![\s\S]+?\*\//,
+ reCustom = /Custom Build/;
+
asyncTest('debug only', function() {
var start = _.once(QUnit.start);
build(['-d', '-s'], function(source, filePath) {
@@ -565,6 +578,9 @@
var start = _.once(QUnit.start);
build(['-d', '-s', 'backbone'], function(source, filePath) {
equal(path.basename(filePath, '.js'), 'lodash.custom');
+
+ var comment = source.match(reComment);
+ ok(reCustom.test(comment));
start();
});
});
@@ -581,6 +597,9 @@
var start = _.once(QUnit.start);
build(['-m', '-s', 'backbone'], function(source, filePath) {
equal(path.basename(filePath, '.js'), 'lodash.custom.min');
+
+ var comment = source.match(reComment);
+ ok(reCustom.test(comment));
start();
});
});
@@ -625,7 +644,51 @@
return pass;
});
- equal(actual, true, basename);
+ ok(actual, basename);
+ start();
+ });
+ });
+ });
+ }());
+
+ /*--------------------------------------------------------------------------*/
+
+ QUnit.module('underscore chaining methods');
+
+ (function() {
+ var commands = [
+ 'backbone',
+ 'underscore'
+ ];
+
+ commands.forEach(function(command) {
+ asyncTest('`lodash ' + command +'`', function() {
+ var start = _.after(2, _.once(QUnit.start));
+
+ build(['-s', command], function(source, filePath) {
+ var basename = path.basename(filePath, '.js'),
+ context = createContext();
+
+ vm.runInContext(source, context);
+ var lodash = context._;
+
+ ok(lodash.chain(1) instanceof lodash, '_.chain: ' + basename);
+ ok(lodash(1).chain() instanceof lodash, '_#chain: ' + basename);
+
+ var wrapped = lodash(1);
+ strictEqual(wrapped.identity(), 1, '_(...) wrapped values are not chainable by default: ' + basename);
+ equal(String(wrapped) === '1', false, '_#toString should not be implemented: ' + basename);
+ equal(Number(wrapped) === 1 , false, '_#valueOf should not be implemented: ' + basename);
+
+ wrapped.chain();
+ ok(wrapped.has('x') instanceof lodash, '_#has returns wrapped values when chaining: ' + basename);
+ ok(wrapped.join() instanceof lodash, '_#join returns wrapped values when chaining: ' + basename);
+
+ wrapped = lodash([1, 2, 3]);
+ ok(wrapped.pop() instanceof lodash, '_#pop returns wrapped values: ' + basename);
+ ok(wrapped.shift() instanceof lodash, '_#shift returns wrapped values: ' + basename);
+ deepEqual(wrapped.splice(0, 0).value(), [2], '_#splice returns wrapper: ' + basename);
+
start();
});
});
@@ -652,10 +715,10 @@
var object = { 'fn': lodash.bind(function(foo) { return foo + this.bar; }, { 'bar': 1 }, 1) };
equal(object.fn(), 2, '_.bind: ' + basename);
- ok(lodash.clone(array, true)[0] === array[0], '_.clone should be shallow: ' + basename);
- equal(lodash.contains({ 'a': 1, 'b': 2 }, 1), true, '_.contains should work with objects: ' + basename);
- equal(lodash.contains([1, 2, 3], 1, 2), true, '_.contains should ignore `fromIndex`: ' + basename);
- equal(lodash.every([true, false, true]), false, '_.every: ' + basename);
+ strictEqual(lodash.clone(array, true)[0], array[0], '_.clone should be shallow: ' + basename);
+ ok(lodash.contains({ 'a': 1, 'b': 2 }, 1), '_.contains should work with objects: ' + basename);
+ ok(lodash.contains([1, 2, 3], 1, 2), '_.contains should ignore `fromIndex`: ' + basename);
+ ok(!lodash.every([true, false, true]), '_.every: ' + basename);
function Foo() {}
Foo.prototype = { 'a': 1 };
@@ -694,18 +757,10 @@
actual = lodash.pick(object, function(value) { return value != 3; });
deepEqual(_.keys(actual), [], '_.pick should not accept a `callback`: ' + basename);
- equal(lodash.some([false, true, false]), true, '_.some: ' + basename);
+ ok(lodash.some([false, true, false]), '_.some: ' + basename);
equal(lodash.template('${a}', object), '${a}', '_.template should ignore ES6 delimiters: ' + basename);
equal(lodash.uniqueId(0), '1', '_.uniqueId should ignore a prefix of `0`: ' + basename);
- var wrapped = lodash(1);
- equal(wrapped.clone() instanceof lodash, false, '_(...) wrapped values are not chainable by default: ' + basename);
- equal(String(wrapped) === '1', false, '_#toString should not be implemented: ' + basename);
- equal(Number(wrapped) === 1 , false, '_#valueOf should not be implemented: ' + basename);
-
- wrapped.chain();
- equal(wrapped.has('x') instanceof lodash, true, '_#has returns wrapped values when chaining: ' + basename);
-
start();
});
});
@@ -806,8 +861,8 @@
case 1:
context.exports = {};
vm.runInContext(source, context);
- ok(context._ === undefined, basename);
- ok(_.isFunction(context.exports._), basename)
+ ok(_.isFunction(context.exports._), basename);
+ strictEqual(context._, undefined, basename);
break;
case 2:
@@ -819,13 +874,13 @@
context.exports = {};
context.module = { 'exports': context.exports };
vm.runInContext(source, context);
- ok(context._ === undefined, basename);
ok(_.isFunction(context.module.exports), basename);
+ strictEqual(context._, undefined, basename);
break;
case 4:
vm.runInContext(source, context);
- ok(context._ === undefined, basename);
+ strictEqual(context._, undefined, basename);
}
start();
});
@@ -874,7 +929,12 @@
QUnit.module('output options');
(function() {
- ['-o a.js', '--output a.js'].forEach(function(command, index) {
+ var commands = [
+ '-o a.js',
+ '--output a.js'
+ ];
+
+ commands.forEach(function(command, index) {
asyncTest('`lodash ' + command +'`', function() {
var start = _.once(QUnit.start);
@@ -891,7 +951,12 @@
QUnit.module('stdout options');
(function() {
- ['-c', '--stdout'].forEach(function(command, index) {
+ var commands = [
+ '-c',
+ '--stdout'
+ ];
+
+ commands.forEach(function(command, index) {
asyncTest('`lodash ' + command +'`', function() {
var written,
start = _.once(QUnit.start),
@@ -931,7 +996,6 @@
} catch(e) {
console.log(e);
}
-
var underscore = context._ || {};
ok(_.isString(underscore.VERSION));
ok(!/Lo-Dash/.test(result) && result.match(/\n/g).length < source.match(/\n/g).length);
@@ -972,7 +1036,7 @@
deepEqual(lodash.merge(object1, object2), object3, basename);
deepEqual(lodash.sortBy([3, 2, 1], _.identity), array, basename);
- equal(lodash.isEqual(circular1, circular2), true, basename);
+ ok(lodash.isEqual(circular1, circular2), basename);
var actual = lodash.clone(circular1, true);
ok(actual != circular1 && actual.b == actual, basename);
diff --git a/test/test.js b/test/test.js
index 623c2ce0cc..c1b12c4707 100644
--- a/test/test.js
+++ b/test/test.js
@@ -243,7 +243,7 @@
/*--------------------------------------------------------------------------*/
- QUnit.module('lodash.clone');
+ QUnit.module('cloning');
(function() {
function Klass() { this.a = 1; }
@@ -277,11 +277,11 @@
_.forOwn(objects, function(object, key) {
test('should deep clone ' + key, function() {
- var clone = _.clone(object, true);
+ var clone = _.cloneDeep(object);
ok(_.isEqual(object, clone));
if (_.isObject(object)) {
- ok(clone !== object);
+ notStrictEqual(clone, object);
} else {
skipTest();
}
@@ -290,8 +290,8 @@
_.forOwn(nonCloneable, function(object, key) {
test('should not clone ' + key, function() {
- ok(_.clone(object) === object);
- ok(_.clone(object, true) === object);
+ strictEqual(_.clone(object), object);
+ strictEqual(_.cloneDeep(object), object);
});
});
@@ -304,7 +304,7 @@
test('should deep clone `index` and `input` array properties', function() {
var array = /x/.exec('x'),
- actual = _.clone(array, true);
+ actual = _.cloneDeep(array);
equal(actual.index, 0);
equal(actual.input, 'x');
@@ -319,15 +319,15 @@
object.foo.b.foo.c = object;
object.bar.b = object.foo.b;
- var clone = _.clone(object, true);
+ var clone = _.cloneDeep(object);
ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c && clone !== object);
});
test('should clone problem JScript properties (test in IE < 9)', function() {
deepEqual(_.clone(shadowed), shadowed);
- ok(_.clone(shadowed) != shadowed);
- deepEqual(_.clone(shadowed, true), shadowed);
- ok(_.clone(shadowed, true) != shadowed);
+ notEqual(_.clone(shadowed), shadowed);
+ deepEqual(_.cloneDeep(shadowed), shadowed);
+ notEqual(_.cloneDeep(shadowed), shadowed);
});
}(1, 2, 3));
@@ -343,7 +343,7 @@
},
function(collection, key) {
test('should work with ' + key + ' and a positive `fromIndex`', function() {
- equal(_.contains(collection, 1, 2), true);
+ ok(_.contains(collection, 1, 2));
});
test('should work with ' + key + ' and a `fromIndex` >= collection\'s length', function() {
@@ -354,12 +354,12 @@
});
test('should work with ' + key + ' and a negative `fromIndex`', function() {
- equal(_.contains(collection, 2, -3), true);
+ ok(_.contains(collection, 2, -3));
});
test('should work with ' + key + ' and a negative `fromIndex` <= negative collection\'s length', function() {
- equal(_.contains(collection, 1, -6), true);
- equal(_.contains(collection, 2, -8), true);
+ ok(_.contains(collection, 1, -6));
+ ok(_.contains(collection, 2, -8));
});
});
@@ -369,8 +369,8 @@
},
function(collection, key) {
test('should work with a string ' + key + ' for `collection`', function() {
- equal(_.contains(collection, 'bc'), true);
- equal(_.contains(collection, 'd'), false);
+ ok(_.contains(collection, 'bc'));
+ ok(!_.contains(collection, 'd'));
});
});
}());
@@ -453,7 +453,7 @@
(function() {
test('should return `false` as soon as the `callback` result is falsey', function() {
- equal(_.every([true, null, true], _.identity), false);
+ ok(!_.every([true, null, true], _.identity));
});
}());
@@ -770,7 +770,7 @@
(function() {
test('should use strict equality in its duck type check', function() {
var element = window.document ? document.body : { 'nodeType': 1 };
- equal(_.isElement(element), true);
+ ok(_.isElement(element));
equal(_.isElement({ 'nodeType': new Number(1) }), false);
equal(_.isElement({ 'nodeType': true }), false);
@@ -794,21 +794,21 @@
test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() {
function Foo() {}
Foo.prototype.a = 1;
- equal(_.isEmpty(Foo), true);
+ ok(_.isEmpty(Foo));
Foo.prototype = { 'a': 1 };
- equal(_.isEmpty(Foo), true);
+ ok(_.isEmpty(Foo));
});
test('should work with an object that has a `length` property', function() {
- equal(_.isEmpty({ 'length': 0 }), false);
+ ok(!_.isEmpty({ 'length': 0 }));
});
test('should work with jQuery/MooTools DOM query collections', function() {
function Foo(elements) { Array.prototype.push.apply(this, elements); }
Foo.prototype = { 'length': 0, 'splice': Array.prototype.splice };
- equal(_.isEmpty(new Foo([])), true);
+ ok(_.isEmpty(new Foo([])));
});
test('should work with `arguments` objects (test in IE < 9)', function() {
@@ -826,8 +826,8 @@
args2 = (function() { return arguments; }(1, 2, 3)),
args3 = (function() { return arguments; }(1, 2));
- equal(_.isEqual(args1, args2), true);
- equal(_.isEqual(args1, args3), false);
+ ok(_.isEqual(args1, args2));
+ ok(!_.isEqual(args1, args3));
});
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() {
@@ -838,7 +838,7 @@
// ensure `_._object` is assigned (unassigned in Opera 10.00)
if (_._object) {
var object = { 'a': 1, 'b': 2, 'c': 3 };
- equal(_.isEqual(object, _._object), true);
+ ok(_.isEqual(object, _._object));
}
else {
skipTest();
@@ -865,18 +865,18 @@
(function() {
test('should return `false` for non-numeric values', function() {
- equal(_.isFinite(null), false);
- equal(_.isFinite([]), false);
- equal(_.isFinite(true), false);
- equal(_.isFinite(''), false);
- equal(_.isFinite(' '), false);
- equal(_.isFinite('2px'), false);
+ ok(!_.isFinite(null));
+ ok(!_.isFinite([]));
+ ok(!_.isFinite(true));
+ ok(!_.isFinite(''));
+ ok(!_.isFinite(' '));
+ ok(!_.isFinite('2px'));
});
test('should return `true` for numeric string values', function() {
- equal(_.isFinite('2'), true);
- equal(_.isFinite('0'), true);
- equal(_.isFinite('08'), true);
+ ok(_.isFinite('2'));
+ ok(_.isFinite('0'));
+ ok(_.isFinite('08'));
});
}());
@@ -906,7 +906,7 @@
(function() {
test('returns `true` for `new Number(NaN)`', function() {
- equal(_.isNaN(new Number(NaN)), true)
+ ok(_.isNaN(new Number(NaN)));
});
}());
@@ -920,9 +920,9 @@
this.a = 1;
}
- equal(_.isPlainObject(new Foo(1)), false);
- equal(_.isPlainObject([1, 2, 3]), false);
- equal(_.isPlainObject({ 'a': 1 }), true);
+ ok(!_.isPlainObject(new Foo(1)));
+ ok(!_.isPlainObject([1, 2, 3]));
+ ok(_.isPlainObject({ 'a': 1 }));
});
}());
@@ -947,7 +947,8 @@
'isRegExp',
'isString',
'isUndefined'
- ], function(methodName) {
+ ],
+ function(methodName) {
var func = _[methodName];
test('lodash.' + methodName + ' should return a boolean', function() {
@@ -1357,7 +1358,7 @@
while ((new Date - start) < 50 && actual == 5) {
actual = _.random(5);
}
- ok(actual != 5);
+ notEqual(actual, 5);
});
}());
@@ -1540,7 +1541,7 @@
(function() {
test('should return `true` as soon as the `callback` result is truthy', function() {
- equal(_.some([null, true, null], _.identity), true);
+ ok(_.some([null, true, null], _.identity));
});
}());
@@ -1667,7 +1668,8 @@
'<%= new Boolean %>': 'false',
'<%= typeof a %>': 'number',
'<%= void a %>': ''
- }, function(value, key) {
+ },
+ function(value, key) {
var compiled = _.template(key),
data = { 'a': 1, 'b': 2 };
@@ -1816,12 +1818,12 @@
actual = counter + 2;
throttled();
throttled();
- }, 64);
+ }, 128);
setTimeout(function() {
equal(counter, actual);
QUnit.start();
- }, 128);
+ }, 256);
ok(counter > 1);
});
@@ -1938,7 +1940,7 @@
test('should coerce the prefix argument to a string', function() {
var actual = [_.uniqueId(3), _.uniqueId(2), _.uniqueId(1)];
- equal(/3\d+,2\d+,1\d+/.test(actual), true);
+ ok(/3\d+,2\d+,1\d+/.test(actual));
});
}());
@@ -2045,24 +2047,20 @@
/*--------------------------------------------------------------------------*/
- QUnit.module('lodash(...) methods capable of returning wrapped and unwrapped values');
+ QUnit.module('lodash(...) methods that return wrapped values');
(function() {
var array = [1, 2, 3],
wrapped = _(array);
var funcs = [
- 'first',
- 'last'
+ 'concat',
+ 'splice'
];
_.each(funcs, function(methodName) {
- test('_.' + methodName + ' should return an unwrapped value', function() {
- equal(typeof wrapped[methodName](), 'number');
- });
-
test('_.' + methodName + ' should return a wrapped value', function() {
- ok(wrapped[methodName](1) instanceof _);
+ ok(wrapped[methodName]() instanceof _);
});
});
}());
@@ -2076,6 +2074,7 @@
wrapped = _(array);
var funcs = [
+ 'clone',
'contains',
'every',
'find',
@@ -2098,19 +2097,46 @@
'isRegExp',
'isString',
'isUndefined',
+ 'join',
'last',
+ 'pop',
+ 'shift',
'reduce',
'reduceRight',
'some'
];
_.each(funcs, function(methodName) {
- test('_.' + methodName + ' should return unwrapped values', function() {
+ test('_.' + methodName + ' should return an unwrapped value', function() {
var result = methodName == 'reduceRight'
? wrapped[methodName](_.identity)
- : wrapped[methodName];
+ : wrapped[methodName]();
+
+ equal(result instanceof _, false);
+ });
+ });
+ }());
+
+ /*--------------------------------------------------------------------------*/
+
+ QUnit.module('lodash(...) methods capable of returning wrapped and unwrapped values');
- notEqual(typeof result, 'object', '_.' + methodName + ' returns unwrapped values');
+ (function() {
+ var array = [1, 2, 3],
+ wrapped = _(array);
+
+ var funcs = [
+ 'first',
+ 'last'
+ ];
+
+ _.each(funcs, function(methodName) {
+ test('_.' + methodName + ' should return an unwrapped value', function() {
+ equal(typeof wrapped[methodName](), 'number');
+ });
+
+ test('_.' + methodName + ' should return a wrapped value', function() {
+ ok(wrapped[methodName](1) instanceof _);
});
});
}());
@@ -2122,19 +2148,20 @@
(function() {
test('should allow falsey arguments', function() {
var returnArrays = [
- 'filter',
- 'invoke',
- 'map',
- 'pluck',
- 'reject',
- 'shuffle',
- 'sortBy',
- 'toArray',
- 'where'
+ 'filter',
+ 'invoke',
+ 'map',
+ 'pluck',
+ 'reject',
+ 'shuffle',
+ 'sortBy',
+ 'toArray',
+ 'where'
];
var funcs = _.without.apply(_, [_.functions(_)].concat([
'_',
+ '_each',
'_iteratorTemplate',
'after',
'bind',
@@ -2220,7 +2247,7 @@
}
if (expected === null) {
- deepEqual(thisArg, null, message);
+ strictEqual(thisArg, null, message);
} else {
equal(thisArg, expected, message);
}
diff --git a/test/underscore.html b/test/underscore.html
index 9f2d1974a9..564c77a1fb 100644
--- a/test/underscore.html
+++ b/test/underscore.html
@@ -5,7 +5,7 @@
Underscore Test Suite
@@ -32,6 +32,62 @@
+
diff --git a/vendor/backbone/backbone.js b/vendor/backbone/backbone.js
index 105f2fc76b..38cc991290 100644
--- a/vendor/backbone/backbone.js
+++ b/vendor/backbone/backbone.js
@@ -1,4 +1,4 @@
-// Backbone.js 0.9.2
+// Backbone.js 0.9.9
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
@@ -19,10 +19,10 @@
var previousBackbone = root.Backbone;
// Create a local reference to array methods.
- var ArrayProto = Array.prototype;
- var push = ArrayProto.push;
- var slice = ArrayProto.slice;
- var splice = ArrayProto.splice;
+ var array = [];
+ var push = array.push;
+ var slice = array.slice;
+ var splice = array.splice;
// The top-level namespace. All public Backbone classes and modules will
// be attached to this. Exported for both CommonJS and the browser.
@@ -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';
// Require Underscore, if we're on the server, and it's not already present.
var _ = root._;
@@ -64,12 +64,49 @@
// Backbone.Events
// ---------------
- // Regular expression used to split event strings
+ // Regular expression used to split event strings.
var eventSplitter = /\s+/;
+ // Implement fancy features of the Events API such as multiple event
+ // names `"change blur"` and jQuery-style event maps `{change: action}`
+ // in terms of the existing API.
+ var eventsApi = function(obj, action, name, rest) {
+ if (!name) return true;
+ if (typeof name === 'object') {
+ for (var key in name) {
+ obj[action].apply(obj, [key, name[key]].concat(rest));
+ }
+ } else if (eventSplitter.test(name)) {
+ var names = name.split(eventSplitter);
+ for (var i = 0, l = names.length; i < l; i++) {
+ obj[action].apply(obj, [names[i]].concat(rest));
+ }
+ } else {
+ return true;
+ }
+ };
+
+ // Optimized internal dispatch function for triggering events. Tries to
+ // keep the usual cases speedy (most Backbone events have 3 arguments).
+ var triggerEvents = function(obj, events, args) {
+ var ev, i = -1, l = events.length;
+ 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, args[0]);
+ return;
+ case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1]);
+ return;
+ case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]);
+ return;
+ default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
+ }
+ };
+
// 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.
+ // 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);
@@ -78,49 +115,58 @@
//
var Events = Backbone.Events = {
- // 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) {
- var calls, event, list;
- if (!callback) return this;
-
- events = events.split(eventSplitter);
- calls = this._callbacks || (this._callbacks = {});
-
- while (event = events.shift()) {
- list = calls[event] || (calls[event] = []);
- list.push(callback, context);
- }
-
+ // Bind one or more space separated events, or an events map,
+ // to a `callback` function. Passing `"all"` will bind the callback to
+ // all events fired.
+ on: function(name, callback, context) {
+ if (!(eventsApi(this, 'on', name, [callback, context]) && callback)) return this;
+ this._events || (this._events = {});
+ var list = this._events[name] || (this._events[name] = []);
+ list.push({callback: callback, context: context, ctx: context || this});
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) {
- var event, calls, list, i;
+ // Bind events to only be triggered a single time. After the first time
+ // the callback is invoked, it will be removed.
+ once: function(name, callback, context) {
+ if (!(eventsApi(this, 'once', name, [callback, context]) && callback)) return this;
+ var self = this;
+ var once = _.once(function() {
+ self.off(name, once);
+ callback.apply(this, arguments);
+ });
+ once._callback = callback;
+ this.on(name, once, context);
+ return this;
+ },
- // No events, or removing *all* events.
- if (!(calls = this._callbacks)) return this;
- if (!(events || callback || context)) {
- delete this._callbacks;
+ // 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(name, callback, context) {
+ var list, ev, events, names, i, l, j, k;
+ if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
+ if (!name && !callback && !context) {
+ this._events = {};
return this;
}
- events = events ? events.split(eventSplitter) : _.keys(calls);
-
- // Loop through the callback list, splicing where appropriate.
- while (event = events.shift()) {
- if (!(list = calls[event]) || !(callback || context)) {
- delete calls[event];
- continue;
- }
-
- for (i = list.length - 2; i >= 0; i -= 2) {
- if (!(callback && list[i] !== callback || context && list[i + 1] !== context)) {
- list.splice(i, 2);
+ names = name ? [name] : _.keys(this._events);
+ for (i = 0, l = names.length; i < l; i++) {
+ name = names[i];
+ if (list = this._events[name]) {
+ events = [];
+ if (callback || context) {
+ for (j = 0, k = list.length; j < k; j++) {
+ ev = list[j];
+ if ((callback && callback !== (ev.callback._callback || ev.callback)) ||
+ (context && context !== ev.context)) {
+ events.push(ev);
+ }
+ }
}
+ this._events[name] = events;
}
}
@@ -131,51 +177,53 @@
// 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).
- trigger: function(events) {
- var event, calls, list, i, length, args, all, rest;
- if (!(calls = this._callbacks)) return this;
-
- rest = [];
- events = events.split(eventSplitter);
-
- // Fill up `rest` with the callback arguments. Since we're only copying
- // the tail of `arguments`, a loop is much faster than Array#slice.
- for (i = 1, length = arguments.length; i < length; i++) {
- rest[i - 1] = arguments[i];
- }
+ trigger: function(name) {
+ if (!this._events) return this;
+ var args = slice.call(arguments, 1);
+ if (!eventsApi(this, 'trigger', name, args)) return this;
+ var events = this._events[name];
+ var allEvents = this._events.all;
+ if (events) triggerEvents(this, events, args);
+ if (allEvents) triggerEvents(this, allEvents, arguments);
+ return this;
+ },
- // For each event, walk through the list of callbacks twice, first to
- // trigger the event, then to trigger any `"all"` callbacks.
- while (event = events.shift()) {
- // Copy callback lists to prevent modification.
- if (all = calls.all) all = all.slice();
- if (list = calls[event]) list = list.slice();
-
- // Execute event callbacks.
- if (list) {
- for (i = 0, length = list.length; i < length; i += 2) {
- list[i].apply(list[i + 1] || this, rest);
- }
- }
+ // An inversion-of-control version of `on`. Tell *this* object to listen to
+ // an event in another object ... keeping track of what it's listening to.
+ listenTo: function(object, events, callback) {
+ var listeners = this._listeners || (this._listeners = {});
+ var id = object._listenerId || (object._listenerId = _.uniqueId('l'));
+ listeners[id] = object;
+ object.on(events, callback || this, this);
+ return this;
+ },
- // Execute "all" callbacks.
- if (all) {
- args = [event].concat(rest);
- for (i = 0, length = all.length; i < length; i += 2) {
- all[i].apply(all[i + 1] || this, args);
- }
+ // Tell this object to stop listening to either specific events ... or
+ // to every object it's currently listening to.
+ stopListening: function(object, events, callback) {
+ var listeners = this._listeners;
+ if (!listeners) return;
+ if (object) {
+ object.off(events, callback, this);
+ if (!events && !callback) delete listeners[object._listenerId];
+ } else {
+ for (var id in listeners) {
+ listeners[id].off(null, null, this);
}
+ this._listeners = {};
}
-
return this;
}
-
};
// Aliases for backwards compatibility.
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
// --------------
@@ -187,17 +235,12 @@
this.cid = _.uniqueId('c');
this.changed = {};
this.attributes = {};
- this._escapedAttributes = {};
- this._modelState = [];
+ this._changes = [];
if (options && options.collection) this.collection = options.collection;
if (options && options.parse) attrs = this.parse(attrs);
- if (defaults = _.result(this, 'defaults')) {
- attrs = _.extend({}, defaults, attrs);
- }
+ if (defaults = _.result(this, 'defaults')) _.defaults(attrs, defaults);
this.set(attrs, {silent: true});
- this._cleanChange = true;
- this._modelState = [];
- this._currentState = _.clone(this.attributes);
+ this._currentAttributes = _.clone(this.attributes);
this._previousAttributes = _.clone(this.attributes);
this.initialize.apply(this, arguments);
};
@@ -208,27 +251,6 @@
// A hash of attributes whose current and previous value differ.
changed: null,
- // Whether there is a pending request to fire in the final `change` loop.
- _pending: false,
-
- // Whether the model is in the midst of a change cycle.
- _changing: false,
-
- // Whether there has been a `set` call since the last
- // calculation of the changed hash, for efficiency.
- _cleanChange: true,
-
- // The model state used for comparison in determining if a
- // change should be fired.
- _currentState: null,
-
- // An array queue of all changes attributed to a model
- // on the next non-silent change event.
- _modelState: null,
-
- // A hash of the model's attributes when the last `change` occured.
- _previousAttributes: null,
-
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`.
idAttribute: 'id',
@@ -254,10 +276,7 @@
// Get the HTML-escaped value of an attribute.
escape: function(attr) {
- var html;
- if (html = this._escapedAttributes[attr]) return html;
- var val = this.get(attr);
- return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
+ return _.escape(this.get(attr));
},
// Returns `true` if the attribute contains a value that is not null
@@ -284,9 +303,6 @@
var silent = options && options.silent;
var unset = options && options.unset;
- if (attrs instanceof Model) attrs = attrs.attributes;
- if (unset) for (attr in attrs) attrs[attr] = void 0;
-
// Run validation.
if (!this._validate(attrs, options)) return false;
@@ -294,24 +310,19 @@
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
var now = this.attributes;
- var esc = this._escapedAttributes;
// For each `set` attribute...
for (attr in attrs) {
val = attrs[attr];
- // If an escaped attr exists, and the new and current value differ, remove the escaped attr.
- if (esc[attr] && !_.isEqual(now[attr], val) || (unset && _.has(now, attr))) delete esc[attr];
-
- // Update or delete the current value.
+ // Update or delete the current value, and track the change.
unset ? delete now[attr] : now[attr] = val;
-
- // Track any action on the attribute.
- this._modelState.push(attr, val, unset);
+ this._changes.push(attr, val);
}
- // Signal that the model's state has potentially changed.
- this._cleanChange = false;
+ // Signal that the model's state has potentially changed, and we need
+ // to recompute the actual changes.
+ this._hasComputed = false;
// Fire the `"change"` events.
if (!silent) this.change(options);
@@ -321,15 +332,15 @@
// Remove an attribute from the model, firing `"change"` unless you choose
// to silence it. `unset` is a noop if the attribute doesn't exist.
unset: function(attr, options) {
- options = _.extend({}, options, {unset: true});
- return this.set(attr, null, options);
+ return this.set(attr, void 0, _.extend({}, options, {unset: true}));
},
// Clear all attributes on the model, firing `"change"` unless you choose
// to silence it.
clear: function(options) {
- options = _.extend({}, options, {unset: true});
- return this.set(_.clone(this.attributes), options);
+ var attrs = {};
+ for (var key in this.attributes) attrs[key] = void 0;
+ return this.set(attrs, _.extend({}, options, {unset: true}));
},
// Fetch the model from the server. If the server's representation of the
@@ -341,7 +352,7 @@
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
- if (!model.set(model.parse(resp, xhr), options)) return false;
+ if (!model.set(model.parse(resp), options)) return false;
if (success) success(model, resp, options);
};
return this.sync('read', this, options);
@@ -383,14 +394,16 @@
var success = options.success;
options.success = function(resp, status, xhr) {
done = true;
- var serverAttrs = model.parse(resp, xhr);
+ var serverAttrs = model.parse(resp);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (!model.set(serverAttrs, options)) return false;
if (success) success(model, resp, options);
};
// 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.
@@ -440,7 +453,7 @@
// **parse** converts a response into the hash of attributes to be `set` on
// the model. The default implementation is just to pass the response along.
- parse: function(resp, xhr) {
+ parse: function(resp) {
return resp;
},
@@ -458,14 +471,15 @@
// a `"change:attribute"` event for each changed attribute.
// Calling this will cause all objects observing the model to update.
change: function(options) {
- var i, changing = this._changing;
+ var changing = this._changing;
this._changing = true;
// Generate the changes to be triggered on the model.
- var triggers = this._changeCenter(true);
- this._pending = triggers.length;
+ var triggers = this._computeChanges(true);
+
+ this._pending = !!triggers.length;
- for (i = triggers.length - 2; i >= 0; i -= 2) {
+ for (var i = triggers.length - 2; i >= 0; i -= 2) {
this.trigger('change:' + triggers[i], this, triggers[i + 1], options);
}
@@ -478,14 +492,14 @@
this._previousAttributes = _.clone(this.attributes);
}
- this._changing = null;
+ this._changing = false;
return this;
},
// Determine if the model has changed since the last `"change"` event.
// If you specify an attribute name, determine if that attribute has changed.
hasChanged: function(attr) {
- if (!this._cleanChange) this._changeCenter();
+ if (!this._hasComputed) this._computeChanges();
if (attr == null) return !_.isEmpty(this.changed);
return _.has(this.changed, attr);
},
@@ -506,39 +520,36 @@
return changed;
},
- // Calculates and handles any changes in `this._modelState`,
- // checking against `this._currentState` to determine current changes.
- _changeCenter: function (change) {
+ // Looking at the built up list of `set` attribute changes, compute how
+ // many of the attributes have actually changed. If `loud`, return a
+ // boiled-down list of only the real changes.
+ _computeChanges: function(loud) {
this.changed = {};
- var local = {};
+ var already = {};
var triggers = [];
- var modelState = this._modelState;
- var currentState = this._currentState;
+ var current = this._currentAttributes;
+ var changes = this._changes;
// Loop through the current queue of potential model changes.
- for (var i = modelState.length - 3; i >= 0; i -= 3) {
- var key = modelState[i], val = modelState[i + 1], unset = modelState[i + 2];
-
- // If the item hasn't been set locally this round, proceed.
- if (!local[key]) {
- local[key] = val;
-
- // Check if the attribute has been modified since the last change,
- // and update `this.changed` accordingly.
- if (currentState[key] !== val || (_.has(currentState, key) && unset)) {
- this.changed[key] = val;
-
- // Triggers & modifications are only created inside a `change` call.
- if (!change) continue;
- triggers.push(key, val);
- (!unset) ? currentState[key] = val : delete currentState[key];
- }
+ for (var i = changes.length - 2; i >= 0; i -= 2) {
+ var key = changes[i], val = changes[i + 1];
+ if (already[key]) continue;
+ already[key] = true;
+
+ // Check if the attribute has been modified since the last change,
+ // and update `this.changed` accordingly. If we're inside of a `change`
+ // call, also add a trigger to the list.
+ if (!_.isEqual(current[key], val)) {
+ this.changed[key] = val;
+ if (!loud) continue;
+ triggers.push(key, val);
+ current[key] = val;
}
- modelState.splice(i,3);
}
+ if (loud) this._changes = [];
// Signals `this.changed` is current to prevent duplicate calls from `this.hasChanged`.
- this._cleanChange = true;
+ this._hasComputed = true;
return triggers;
},
@@ -555,17 +566,11 @@
return _.clone(this._previousAttributes);
},
- // Check if the model is currently in a valid state. It's only possible to
- // get into an *invalid* state if you're using silent changes.
- isValid: function(options) {
- return !this.validate || !this.validate(this.attributes, options);
- },
-
// Run validation against the next complete set of model attributes,
// 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 +593,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.
@@ -619,8 +621,9 @@
// Add a model, or list of models to the set. Pass **silent** to avoid
// firing the `add` event for every new model.
add: function(models, options) {
- var i, args, length, model, existing, sort;
+ var i, args, length, model, existing, needsSort;
var at = options && options.at;
+ var sort = ((options && options.sort) == null ? true : options.sort);
models = _.isArray(models) ? models.slice() : [models];
// Turn bare objects into model references, and prevent invalid models
@@ -638,8 +641,8 @@
// optionally merge it into the existing model.
if (existing || this._byCid[model.cid]) {
if (options && options.merge && existing) {
- existing.set(model, options);
- sort = true;
+ existing.set(model.attributes, options);
+ needsSort = sort;
}
models.splice(i, 1);
continue;
@@ -653,14 +656,14 @@
}
// See if sorting is needed, update `length` and splice in new models.
- if (models.length) sort = true;
+ if (models.length) needsSort = sort;
this.length += models.length;
args = [at != null ? at : this.models.length, 0];
push.apply(args, models);
splice.apply(this.models, args);
// Sort the collection if appropriate.
- if (sort && this.comparator && at == null) this.sort({silent: true});
+ if (needsSort && this.comparator && at == null) this.sort({silent: true});
if (options && options.silent) return this;
@@ -679,7 +682,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];
@@ -698,7 +701,7 @@
// Add a model to the end of the collection.
push: function(model, options) {
model = this._prepareModel(model, options);
- this.add(model, options);
+ this.add(model, _.extend({at: this.length}, options));
return model;
},
@@ -729,14 +732,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 +767,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 +776,56 @@
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, existing;
+ var add = [], remove = [], modelMap = {};
+ var idAttr = this.model.prototype.idAttribute;
+ options = _.extend({add: true, merge: true, remove: true}, options);
+ if (options.parse) models = this.parse(models);
+
+ // Allow a single model (or no argument) to be passed.
+ if (!_.isArray(models)) models = models ? [models] : [];
+
+ // Proxy to `add` for this case, no need to iterate...
+ if (options.add && !options.remove) return this.add(models, 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.id || model.cid || model[idAttr]);
+ if (options.remove && existing) modelMap[existing.cid] = true;
+ if ((options.add && !existing) || (options.merge && existing)) {
+ add.push(model);
+ }
+ }
+ if (options.remove) {
+ for (i = 0, l = this.models.length; i < l; i++) {
+ model = this.models[i];
+ if (!modelMap[model.cid]) 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 +838,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](resp, options);
if (success) success(collection, resp, options);
};
return this.sync('read', this, options);
@@ -826,7 +865,7 @@
// **parse** converts a response into a list of models to be added to the
// collection. The default implementation is just to pass it through.
- parse: function(resp, xhr) {
+ parse: function(resp) {
return resp;
},
@@ -859,7 +898,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 +971,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, {
@@ -1003,7 +1042,7 @@
this.handlers = [];
_.bindAll(this, 'checkUrl');
- // #1653 - Ensure that `History` can be used outside of the browser.
+ // Ensure that `History` can be used outside of the browser.
if (typeof window !== 'undefined') {
this.location = window.location;
this.history = window.history;
@@ -1051,7 +1090,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
@@ -1199,7 +1238,7 @@
var href = location.href.replace(/(javascript:|#).*$/, '');
location.replace(href + '#' + fragment);
} else {
- // #1649 - Some browsers require that `hash` contains a leading #.
+ // Some browsers require that `hash` contains a leading #.
location.hash = '#' + fragment;
}
}
@@ -1251,20 +1290,11 @@
return this;
},
- // Clean up references to this view in order to prevent latent effects and
- // memory leaks.
- dispose: function() {
- this.undelegateEvents();
- if (this.model && this.model.off) this.model.off(null, null, this);
- if (this.collection && this.collection.off) this.collection.off(null, null, this);
- return this;
- },
-
- // Remove this view from the DOM. Note that the view isn't present in the
- // DOM by default, so calling this method may be a no-op.
+ // Remove this view by taking the element out of the DOM, and removing any
+ // applicable Backbone.Events listeners.
remove: function() {
- this.dispose();
this.$el.remove();
+ this.stopListening();
return this;
},
@@ -1364,6 +1394,7 @@
var methodMap = {
'create': 'POST',
'update': 'PUT',
+ 'patch': 'PATCH',
'delete': 'DELETE',
'read': 'GET'
};
@@ -1401,9 +1432,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' || method === 'patch')) {
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.
@@ -1414,7 +1445,7 @@
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
// And an `X-HTTP-Method-Override` header.
- if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE')) {
+ if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
params.type = 'POST';
if (options.emulateJSON) params.data._method = type;
var beforeSend = options.beforeSend;
@@ -1442,7 +1473,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 `$`.
diff --git a/vendor/backbone/test/collection.js b/vendor/backbone/test/collection.js
index cabb65ca4c..339a50e8b2 100644
--- a/vendor/backbone/test/collection.js
+++ b/vendor/backbone/test/collection.js
@@ -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);
});
@@ -382,6 +386,19 @@ $(document).ready(function() {
equal(this.syncArgs.options.parse, false);
});
+ test("ensure fetch only parses once", 1, function() {
+ var collection = new Backbone.Collection;
+ var counter = 0;
+ collection.parse = function(models) {
+ counter++;
+ return models;
+ };
+ collection.url = '/test';
+ collection.fetch();
+ this.syncArgs.options.success([]);
+ equal(counter, 1);
+ });
+
test("create", 4, function() {
var collection = new Backbone.Collection;
collection.url = '/test';
@@ -542,7 +559,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 +584,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 +743,171 @@ $(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});
+ 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, 2);
+ strictEqual(m2.get('a'), 1);
+
+ // Test removing models not passing an argument
+ c.off('remove').on('remove', function(model) {
+ ok(model === m2 || model === m3);
+ });
+ c.update([]);
+ strictEqual(c.length, 0);
+ });
+
+ test("update with only cids", 3, function() {
+ var m1 = new Backbone.Model;
+ var m2 = new Backbone.Model;
+ var c = new Backbone.Collection;
+ c.update([m1, m2]);
+ equal(c.length, 2);
+ c.update([m1]);
+ equal(c.length, 1);
+ c.update([m1, m1, m1, m2, m2], {remove: false});
+ equal(c.length, 2);
+ });
+
+ test("update with only idAttribute", 3, function() {
+ var m1 = { _id: 1 };
+ var m2 = { _id: 2 };
+ var col = Backbone.Collection.extend({
+ model: Backbone.Model.extend({
+ idAttribute: '_id'
+ })
+ });
+ var c = new col;
+ c.update([m1, m2]);
+ equal(c.length, 2);
+ c.update([m1]);
+ equal(c.length, 1);
+ c.update([m1, m1, m1, m2, m2], {remove: false});
+ equal(c.length, 2);
+ });
+
+ test("#1894 - Push should not trigger a sort", 0, function() {
+ var Collection = Backbone.Collection.extend({
+ comparator: 'id',
+ sort: function() {
+ ok(false);
+ }
+ });
+ new Collection().push({id: 1});
+ });
+
+ // test("`update` with non-normal id", function() {
+ // var Collection = Backbone.Collection.extend({
+ // model: Backbone.Model.extend({idAttribute: '_id'})
+ // });
+ // var collection = new Collection({_id: 1});
+ // collection.update([{_id: 1, a: 1}], {add: false});
+ // equal(collection.first().get('a'), 1);
+ // });
+
+ test("#1894 - `sort` can optionally be turned off", 0, function() {
+ var Collection = Backbone.Collection.extend({
+ comparator: 'id',
+ sort: function() { ok(true); }
+ });
+ new Collection().add({id: 1}, {sort: false});
+ });
+
+ test("#1915 - `parse` data in the right order in `update`", 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.update(res, {parse: true});
+ });
+
});
diff --git a/vendor/backbone/test/events.js b/vendor/backbone/test/events.js
index c10a728364..c3f1d61dd0 100644
--- a/vendor/backbone/test/events.js
+++ b/vendor/backbone/test/events.js
@@ -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,57 @@ $(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("listenTo and stopListening", 1, function() {
+ var a = _.extend({}, Backbone.Events);
+ var b = _.extend({}, Backbone.Events);
+ a.listenTo(b, 'all', function(){ ok(true); });
+ b.trigger('anything');
+ a.listenTo(b, 'all', function(){ ok(false); });
+ a.stopListening();
+ b.trigger('anything');
+ });
+
+ test("listenTo and stopListening with event maps", 1, function() {
+ var a = _.extend({}, Backbone.Events);
+ var b = _.extend({}, Backbone.Events);
+ a.listenTo(b, {change: function(){ ok(true); }});
+ b.trigger('change');
+ a.listenTo(b, {change: function(){ ok(false); }});
+ a.stopListening();
+ b.trigger('change');
+ });
+
test("trigger all for each event", 3, function() {
var a, b, obj = { counter: 0 };
_.extend(obj, Backbone.Events);
@@ -192,4 +243,121 @@ $(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');
+ 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("once with off only by context", 0, function() {
+ var context = {};
+ var obj = _.extend({}, Backbone.Events);
+ obj.once('event', function(){ ok(false); }, context);
+ obj.off(null, null, context);
+ obj.trigger('event');
+ });
+
+ 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');
+ });
+
+ test("once with multiple events.", 2, function() {
+ var obj = _.extend({}, Backbone.Events);
+ obj.once('x y', function() { ok(true); });
+ obj.trigger('x y');
+ });
+
+ test("Off during iteration with once.", 2, function() {
+ var obj = _.extend({}, Backbone.Events);
+ var f = function(){ this.off('event', f); };
+ obj.on('event', f);
+ obj.once('event', function(){});
+ obj.on('event', function(){ ok(true); });
+
+ obj.trigger('event');
+ obj.trigger('event');
+ });
+
+ test("`once` on `all` should work as expected", 1, function() {
+ Backbone.once('all', function() {
+ ok(true);
+ Backbone.trigger('all');
+ });
+ Backbone.trigger('all');
+ });
+
});
diff --git a/vendor/backbone/test/model.js b/vendor/backbone/test/model.js
index fd0be2d5b3..c097298016 100644
--- a/vendor/backbone/test/model.js
+++ b/vendor/backbone/test/model.js
@@ -55,6 +55,18 @@ $(document).ready(function() {
equal(model.get('value'), 2);
});
+ test("initialize with defaults", 2, function() {
+ var Model = Backbone.Model.extend({
+ defaults: {
+ first_name: 'Unknown',
+ last_name: 'Unknown'
+ }
+ });
+ var model = new Model({'first_name': 'John'});
+ equal(model.get('first_name'), 'John');
+ equal(model.get('last_name'), 'Unknown');
+ });
+
test("parse can return null", 1, function() {
var Model = Backbone.Model.extend({
parse: function(obj) {
@@ -114,7 +126,7 @@ $(document).ready(function() {
var foo = new Backbone.Model({p: 1});
var bar = new Backbone.Model({p: 2});
- bar.set(foo.clone(), {unset: true});
+ bar.set(foo.clone().attributes, {unset: true});
equal(foo.get('p'), 1);
equal(bar.get('p'), undefined);
});
@@ -201,6 +213,27 @@ $(document).ready(function() {
equal(a.id, undefined, "Unsetting the id should remove the id property.");
});
+ test("set triggers changes in the correct order", function() {
+ var value = null;
+ var model = new Backbone.Model;
+ model.on('last', function(){ value = 'last'; });
+ model.on('first', function(){ value = 'first'; });
+ model.trigger('first');
+ model.trigger('last');
+ equal(value, 'last');
+ });
+
+ test("set falsy values in the correct order", 1, function() {
+ var model = new Backbone.Model({result: 'result'});
+ model.on('change', function() {
+ equal(model.changed.result, false);
+ });
+ model.set({result: void 0}, {silent: true});
+ model.set({result: null}, {silent: true});
+ model.set({result: false}, {silent: true});
+ model.change();
+ });
+
test("multiple unsets", 1, function() {
var i = 0;
var counter = function(){ i++; };
@@ -261,7 +294,7 @@ $(document).ready(function() {
});
var model = new Defaulted({two: null});
equal(model.get('one'), 1);
- equal(model.get('two'), null);
+ equal(model.get('two'), 2);
Defaulted = Backbone.Model.extend({
defaults: function() {
return {
@@ -270,9 +303,9 @@ $(document).ready(function() {
};
}
});
- var model = new Defaulted({two: null});
+ model = new Defaulted({two: null});
equal(model.get('one'), 3);
- equal(model.get('two'), null);
+ equal(model.get('two'), 4);
});
test("change, hasChanged, changedAttributes, previous, previousAttributes", 12, function() {
@@ -293,7 +326,6 @@ $(document).ready(function() {
equal(model.hasChanged('name'), true);
model.change();
equal(model.get('name'), 'Rob');
-
});
test("changedAttributes", 3, function() {
@@ -351,24 +383,26 @@ $(document).ready(function() {
equal(lastError, "Can't change admin status.");
});
- test("isValid", 5, function() {
- var model = new Backbone.Model({valid: true});
- model.validate = function(attrs) {
- if (!attrs.valid) return "invalid";
- };
- 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);
- });
-
test("save", 2, function() {
doc.save({title : "Henry V"});
equal(this.syncArgs.method, 'update');
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);
+ equal(this.ajaxSettings.data, "{\"b\":2,\"d\":4}");
+ });
+
test("save in positional style", 1, function() {
var model = new Backbone.Model();
model.sync = function(method, model, options) {
@@ -402,7 +436,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 +449,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);
@@ -560,9 +592,9 @@ $(document).ready(function() {
ok(model.get('x') === a);
});
- test("unset fires change for undefined attributes", 1, function() {
+ test("unset does not fire a change for undefined attributes", 0, function() {
var model = new Backbone.Model({x: undefined});
- model.on('change:x', function(){ ok(true); });
+ model.on('change:x', function(){ ok(false); });
model.unset('x');
});
@@ -639,7 +671,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); };
@@ -774,12 +806,6 @@ $(document).ready(function() {
model.set({a: true});
});
- test("#1179 - isValid returns true in the absence of validate.", 1, function() {
- var model = new Backbone.Model();
- model.validate = null;
- ok(model.isValid());
- });
-
test("#1122 - clear does not alter options.", 1, function() {
var model = new Backbone.Model();
var options = {};
@@ -917,4 +943,10 @@ $(document).ready(function() {
deepEqual(changes, ['a']);
});
+ test("#1943 change calculations should use _.isEqual", function() {
+ var model = new Backbone.Model({a: {key: 'value'}});
+ model.set('a', {key:'value'}, {silent:true});
+ equal(model.changedAttributes(), false);
+ });
+
});
diff --git a/vendor/backbone/test/router.js b/vendor/backbone/test/router.js
index f9327fcc35..50be7effb7 100644
--- a/vendor/backbone/test/router.js
+++ b/vendor/backbone/test/router.js
@@ -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');
});
@@ -207,7 +207,7 @@ $(document).ready(function() {
test("routes (optional)", 2, function() {
location.replace('http://example.com#optional');
Backbone.history.checkUrl();
- equal(router.arg, null);
+ ok(!router.arg);
location.replace('http://example.com#optional/thing');
Backbone.history.checkUrl();
equal(router.arg, 'thing');
@@ -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');
});
diff --git a/vendor/backbone/test/view.js b/vendor/backbone/test/view.js
index 400c80eb6d..444d90b2c4 100644
--- a/vendor/backbone/test/view.js
+++ b/vendor/backbone/test/view.js
@@ -41,7 +41,7 @@ $(document).ready(function() {
var div = view.make('div', {id: 'test-div'}, 0);
equal($(div).text(), '0');
- var div = view.make('div', {id: 'test-div'}, '');
+ div = view.make('div', {id: 'test-div'}, '');
equal($(div).text(), '');
});
@@ -171,7 +171,7 @@ $(document).ready(function() {
return {
title: 'title1',
acceptText: 'confirm'
- }
+ };
}
});
@@ -310,14 +310,11 @@ $(document).ready(function() {
ok(new View().$el.is('p'));
});
- test("dispose", 0, function() {
+ test("views stopListening", 0, function() {
var View = Backbone.View.extend({
- events: {
- click: function() { ok(false); }
- },
initialize: function() {
- this.model.on('all x', function(){ ok(false); }, this);
- this.collection.on('all x', function(){ ok(false); }, this);
+ this.listenTo(this.model, 'all x', function(){ ok(false); }, this);
+ this.listenTo(this.collection, 'all x', function(){ ok(false); }, this);
}
});
@@ -326,22 +323,9 @@ $(document).ready(function() {
collection: new Backbone.Collection
});
- view.dispose();
+ view.stopListening();
view.model.trigger('x');
view.collection.trigger('x');
- view.$el.click();
- });
-
- test("dispose with non Backbone objects", 0, function() {
- var view = new Backbone.View({model: {}, collection: {}});
- view.dispose();
- });
-
- test("view#remove calls dispose.", 1, function() {
- var view = new Backbone.View();
-
- view.dispose = function() { ok(true); };
- view.remove();
});
test("Provide function for el.", 1, function() {
@@ -364,16 +348,16 @@ $(document).ready(function() {
counter++;
}
});
-
+
var view = new View({events:{'click #test':'increment'}});
var view2 = new View({events:function(){
return {'click #test':'increment'};
}});
-
+
view.$('#test').trigger('click');
view2.$('#test').trigger('click');
equal(counter, 2);
-
+
view.$('#test').trigger('click');
view2.$('#test').trigger('click');
equal(counter, 4);
diff --git a/vendor/benchmark.js/benchmark.js b/vendor/benchmark.js/benchmark.js
index 5b63f8baa0..fe5ff3db06 100644
--- a/vendor/benchmark.js/benchmark.js
+++ b/vendor/benchmark.js/benchmark.js
@@ -25,6 +25,9 @@
/** Detect free variable `require` */
var freeRequire = typeof require == 'function' && require;
+ /** Used to store the `Object` built-in in case it's overwritten later */
+ var Object = window.Object;
+
/** Used to crawl all properties regardless of enumerability */
var getAllKeys = Object.getOwnPropertyNames;
diff --git a/vendor/docdown/src/DocDown/Entry.php b/vendor/docdown/src/DocDown/Entry.php
index 933bf4005d..e545d33607 100644
--- a/vendor/docdown/src/DocDown/Entry.php
+++ b/vendor/docdown/src/DocDown/Entry.php
@@ -187,7 +187,10 @@ public function getDesc() {
preg_match('#/\*\*(?:\s*\*)?([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
if (count($result)) {
$type = $this->getType();
- $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
+ $result = preg_replace('/:\n *\* */', ":
\n", $result[1]);
+ $result = preg_replace('/(?:^|\n) *\*\n *\* */', "\n\n", $result);
+ $result = preg_replace('/(?:^|\n) *\* ?/', ' ', $result);
+ $result = trim($result);
$result = ($type == 'Function' ? '' : '(' . str_replace('|', ', ', trim($type, '{}')) . '): ') . $result;
}
$this->_desc = $result;
diff --git a/vendor/firebug-lite/skin/xp/firebug-1.3a2.css b/vendor/firebug-lite/skin/xp/firebug-1.3a2.css
index 42f9faf5d5..d929b2bf17 100644
--- a/vendor/firebug-lite/skin/xp/firebug-1.3a2.css
+++ b/vendor/firebug-lite/skin/xp/firebug-1.3a2.css
@@ -70,7 +70,7 @@ html, body {
body {
font-family: Lucida Grande, Tahoma, sans-serif;
font-size: 11px;
- background: #fff;
+ background: #fff;
}
.clear {
@@ -103,7 +103,7 @@ body {
border: 1px solid #ccc;
margin: 0 5px 0 0;
background: #fff url(search.png) no-repeat 4px 2px;
- padding-left: 20px;
+ padding-left: 20px;
font-size: 11px;
}
@@ -124,7 +124,7 @@ body {
height: 14px;
background: url(errorIcon.png) no-repeat;
color: #f00;
- font-weight: bold;
+ font-weight: bold;
}
#fbMiniErrors {
@@ -139,7 +139,7 @@ body {
margin: 3px 4px 0;
height: 20px;
width: 20px;
- float: right;
+ float: right;
background: url(sprite.png) 0 -135px;
cursor: pointer;
}
@@ -192,10 +192,10 @@ body {
}
/************************************************************************************************
- Sub-Layout
+ Sub-Layout
*************************************************************************************************/
-/* fbToolbar
+/* fbToolbar
*************************************************************************************************/
#fbToolbarIcon {
float: left;
@@ -237,7 +237,7 @@ body {
position: relative;
top: 5px;
line-height: 19px;
- cursor: default;
+ cursor: default;
}
.fbToolbarSeparator{
@@ -262,7 +262,7 @@ body {
.fbStatusBar span a:hover {
color: blue;
- cursor: pointer;
+ cursor: pointer;
}
@@ -307,7 +307,7 @@ body {
padding-left: 10px;
}
-/* body
+/* body
*************************************************************************************************/
.fbPanel {
display: none;
@@ -340,7 +340,7 @@ body {
visibility: hidden !important;
}
-/* fbBottom
+/* fbBottom
*************************************************************************************************/
#fbCommand {
@@ -391,7 +391,7 @@ div.fbFitHeight {
Layout Controls
*************************************************************************************************/
-/* fbToolbar buttons
+/* fbToolbar buttons
*************************************************************************************************/
#fbWindowButtons a {
font-size: 1px;
@@ -420,7 +420,7 @@ div.fbFitHeight {
background: url(sprite.png) -48px -119px;
}
-/* fbPanelBarBox tabs
+/* fbPanelBarBox tabs
*************************************************************************************************/
.fbTab {
text-decoration: none;
@@ -475,7 +475,7 @@ a.fbTab:hover .fbTabR {
background: url(sprite.png) -8px -96px !important;
}
-/* splitters
+/* splitters
*************************************************************************************************/
#fbHSplitter {
position: absolute;
@@ -571,7 +571,7 @@ div.objectBox-element {
color: #fff !important;
}
-/* Webkit CSS Hack - bug in "highlight" named color */
+/* Webkit CSS Hack - bug in "highlight" named color */
@media screen and (-webkit-min-device-pixel-ratio:0) {
.selectedElement{
background: #316AC5;
diff --git a/vendor/firebug-lite/skin/xp/firebug.css b/vendor/firebug-lite/skin/xp/firebug.css
index a1465ec5f3..f3bdd8cdee 100644
--- a/vendor/firebug-lite/skin/xp/firebug.css
+++ b/vendor/firebug-lite/skin/xp/firebug.css
@@ -136,10 +136,10 @@ h1.groupHeader {
position: relative;
top: -7px;
left: -5px;
-
+
outline: none;
resize: none;
-
+
/*
_border: 1px solid #999 !important;
_padding: 1px !important;
@@ -311,17 +311,17 @@ h1.groupHeader {
outline: none;
background-color: transparent
}
-
- .useA11y .a11yCSSView .focusRow:focus .cssSelector,
- .useA11y .a11yCSSView .focusRow:focus .cssPropName,
+
+ .useA11y .a11yCSSView .focusRow:focus .cssSelector,
+ .useA11y .a11yCSSView .focusRow:focus .cssPropName,
.useA11y .a11yCSSView .focusRow:focus .cssPropValue,
- .useA11y .a11yCSSView .computedStyleRow:focus,
+ .useA11y .a11yCSSView .computedStyleRow:focus,
.useA11y .a11yCSSView .groupHeader:focus {
outline: 2px solid #FF9933;
outline-offset: -2px;
background-color: #FFFFD6;
}
-
+
.useA11y .a11yCSSView .groupHeader:focus {
outline-offset: -2px;
}
@@ -731,7 +731,7 @@ h1.groupHeader {
/* Time Info tip */
.timeInfoTip {
- width: 150px;
+ width: 150px;
height: 40px
}
@@ -944,7 +944,7 @@ h1.groupHeader {
/*overflow-x: auto; HTML is damaged in case of big (2-3MB) responses */
}
-/* replaced by .netInfoTextSelected for IE6 support
+/* replaced by .netInfoTextSelected for IE6 support
.netInfoText[selected="true"] {
display: block;
}
@@ -963,7 +963,7 @@ h1.groupHeader {
}
.netInfoPostText .netInfoParamName {
- width: 1px; /* Google Chrome need this otherwise the first column of
+ width: 1px; /* Google Chrome need this otherwise the first column of
the post variables table will be larger than expected */
}
@@ -1148,7 +1148,7 @@ h1.groupHeader {
}
* html .opened .spyHead .spyTitle,
-* html .opened .logGroupLabel,
+* html .opened .logGroupLabel,
* html .opened .memberLabelCell .memberLabel {
background-image: url(tree_close.gif);
background-repeat: no-repeat;
@@ -1677,7 +1677,7 @@ h1.groupHeader {
/*
.logRow-errorMessage > .hasTwisty > .errorTitle,
.logRow-spy .spyHead .spyTitle,
-.logGroup > .logRow
+.logGroup > .logRow
*/
.logRow-errorMessage .hasTwisty .errorTitle,
.logRow-spy .spyHead .spyTitle,
@@ -2056,11 +2056,11 @@ h1.groupHeader {
width: 10px;
height: 10px;
margin-top: 6px;
- background: url(tabMenuTarget.png);
+ background: url(tabMenuTarget.png);
}
.fbTabMenuTarget:hover {
- background: url(tabMenuTargetHover.png);
+ background: url(tabMenuTargetHover.png);
}
.fbShadow {
@@ -2098,7 +2098,7 @@ h1.groupHeader {
padding: 1px 18px 0;
text-decoration: none;
color: #000;
- cursor: default;
+ cursor: default;
background: #ACA899;
margin: 4px 0;
}
@@ -2149,7 +2149,7 @@ h1.groupHeader {
}
.fbMenuShortcut {
- padding-right: 85px;
+ padding-right: 85px;
}
.fbMenuShortcutKey {
@@ -2274,7 +2274,7 @@ h1.groupHeader {
margin: 0;
padding: 0;
overflow: hidden;
-
+
font-family: Lucida Grande, Tahoma, sans-serif;
font-size: 11px;
background: #fff;
@@ -2311,7 +2311,7 @@ h1.groupHeader {
margin: 0 5px 0 0;
background: #fff url(search.png) no-repeat 4px 2px !important;
background: #fff url(search.gif) no-repeat 4px 2px;
- padding-left: 20px;
+ padding-left: 20px;
font-size: 11px;
}
@@ -2333,7 +2333,7 @@ h1.groupHeader {
background: url(errorIcon.png) no-repeat !important;
background: url(errorIcon.gif) no-repeat;
color: #f00;
- font-weight: bold;
+ font-weight: bold;
}
#fbMiniErrors {
@@ -2348,7 +2348,7 @@ h1.groupHeader {
margin: 3px 4px 0;
height: 20px;
width: 20px;
- float: right;
+ float: right;
background: url(sprite.png) 0 -135px;
cursor: pointer;
}
@@ -2403,10 +2403,10 @@ h1.groupHeader {
}
/************************************************************************************************
- Sub-Layout
+ Sub-Layout
*************************************************************************************************/
-/* fbToolbar
+/* fbToolbar
*************************************************************************************************/
#fbToolbarIcon {
float: left;
@@ -2472,7 +2472,7 @@ h1.groupHeader {
#fbStatusBarBox {
top: 4px;
- cursor: default;
+ cursor: default;
}
.fbToolbarSeparator {
@@ -2500,7 +2500,7 @@ h1.groupHeader {
.fbStatusBar a:hover {
color: blue;
- cursor: pointer;
+ cursor: pointer;
}
@@ -2545,7 +2545,7 @@ h1.groupHeader {
padding-left: 4px;
}
-/* body
+/* body
*************************************************************************************************/
.fbPanel {
display: none;
@@ -2609,7 +2609,7 @@ h1.groupHeader {
position: absolute;
right: 2px;
bottom: 3px;
-
+
z-index: 99;
}
@@ -2624,7 +2624,7 @@ h1.groupHeader {
visibility: hidden !important;
}
-/* fbBottom
+/* fbBottom
*************************************************************************************************/
#fbCommand {
@@ -2689,7 +2689,7 @@ div.fbFitHeight {
Layout Controls
*************************************************************************************************/
-/* fbToolbar buttons
+/* fbToolbar buttons
*************************************************************************************************/
.fbSmallButton {
overflow: hidden;
@@ -2729,7 +2729,7 @@ div.fbFitHeight {
}
-/* fbPanelBarBox tabs
+/* fbPanelBarBox tabs
*************************************************************************************************/
.fbTab {
text-decoration: none;
@@ -2785,7 +2785,7 @@ a.fbTab:hover .fbTabR {
background: url(sprite.png) -8px -96px !important;
}
-/* splitters
+/* splitters
*************************************************************************************************/
#fbHSplitter {
position: fixed;
@@ -2888,7 +2888,7 @@ div.objectBox-element {
position: relative;
}
-/* Webkit CSS Hack - bug in "highlight" named color */
+/* Webkit CSS Hack - bug in "highlight" named color */
@media screen and (-webkit-min-device-pixel-ratio:0) {
.selectedElement{
background: #316AC5;
@@ -2903,7 +2903,7 @@ div.objectBox-element {
}
/* TODO: remove this? */
-/* TODO: xxxpedro - IE need this in windowless mode (cnn.com) check if the issue is related to
+/* TODO: xxxpedro - IE need this in windowless mode (cnn.com) check if the issue is related to
position. if so, override it at chrome.js initialization when creating the div */
.logRow {
position: relative;
@@ -2939,9 +2939,9 @@ position. if so, override it at chrome.js initialization when creating the div *
.objectBox-string {
color: red;
-
+
/* TODO: xxxpedro make long strings break line */
- /*white-space: pre; */
+ /*white-space: pre; */
}
.objectBox-number {
diff --git a/vendor/firebug-lite/skin/xp/firebug.html b/vendor/firebug-lite/skin/xp/firebug.html
index aa078099fb..c2b5c43472 100644
--- a/vendor/firebug-lite/skin/xp/firebug.html
+++ b/vendor/firebug-lite/skin/xp/firebug.html
@@ -14,71 +14,71 @@
-
-
-
+
-
+
-
+
|
-
+
-
+
|
-
+
-
+
-
+
-
+
-
+
-
+
|
-
+
-
+
-
+
|
-
+
-
+
-
+
-