mirror of
https://github.com/jashkenas/backbone.git
synced 2026-04-30 03:00:06 -04:00
Merged in 1.4.0
This commit is contained in:
11
.eslintrc
11
.eslintrc
@@ -5,11 +5,11 @@
|
||||
"amd": true
|
||||
},
|
||||
"globals": {
|
||||
"attachEvent": true,
|
||||
"detachEvent": true
|
||||
"attachEvent": false,
|
||||
"detachEvent": false
|
||||
},
|
||||
"rules": {
|
||||
"array-bracket-spacing": [2],
|
||||
"array-bracket-spacing": 2,
|
||||
"block-scoped-var": 2,
|
||||
"brace-style": [1, "1tbs", {"allowSingleLine": true}],
|
||||
"camelcase": 2,
|
||||
@@ -21,6 +21,7 @@
|
||||
"eqeqeq": [2, "smart"],
|
||||
"indent": [2, 2, {"SwitchCase": 1, "VariableDeclarator": 2}],
|
||||
"key-spacing": 1,
|
||||
"keyword-spacing": [2, { "after": true }],
|
||||
"linebreak-style": 2,
|
||||
"max-depth": [1, 4],
|
||||
"max-params": [1, 5],
|
||||
@@ -37,7 +38,6 @@
|
||||
"no-duplicate-case": 2,
|
||||
"no-else-return": 1,
|
||||
"no-empty-character-class": 2,
|
||||
"no-empty-label": 2,
|
||||
"no-eval": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extend-native": 2,
|
||||
@@ -51,6 +51,7 @@
|
||||
"no-inner-declarations": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-label-var": 2,
|
||||
"no-labels": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-lonely-if": 2,
|
||||
"no-multi-str": 2,
|
||||
@@ -79,10 +80,8 @@
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
"radix": 2,
|
||||
"semi": 2,
|
||||
"space-after-keywords": [2, "always"],
|
||||
"space-before-function-paren": [2, {"anonymous": "never", "named": "never"}],
|
||||
"space-infix-ops": 2,
|
||||
"space-return-throw-case": 2,
|
||||
"space-unary-ops": [2, { "words": true, "nonwords": false }],
|
||||
"use-isnan": 2,
|
||||
"valid-typeof": 2,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "6"
|
||||
before_install:
|
||||
- npm install -g npm
|
||||
- npm install -g karma-cli
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2010-2016 Jeremy Ashkenas, DocumentCloud
|
||||
Copyright (c) 2010-2019 Jeremy Ashkenas, DocumentCloud
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
|
||||
2
backbone-min.js
vendored
2
backbone-min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
438
backbone.js
438
backbone.js
@@ -1,6 +1,6 @@
|
||||
// Backbone.js 1.3.3
|
||||
// Backbone.js 1.4.0
|
||||
|
||||
// (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||
// (c) 2010-2019 Jeremy Ashkenas and DocumentCloud
|
||||
// Backbone may be freely distributed under the MIT license.
|
||||
// For all details and documentation:
|
||||
// http://backbonejs.org
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
// Establish the root object, `window` (`self`) in the browser, or `global` on the server.
|
||||
// We use `self` instead of `window` for `WebWorker` support.
|
||||
var root = (typeof self == 'object' && self.self === self && self) ||
|
||||
(typeof global == 'object' && global.global === global && global);
|
||||
var root = typeof self == 'object' && self.self === self && self ||
|
||||
typeof global == 'object' && global.global === global && global;
|
||||
|
||||
// Set up Backbone appropriately for the environment. Start with AMD.
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
// Finally, as a browser global.
|
||||
} else {
|
||||
root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
|
||||
root.Backbone = factory(root, {}, root._, root.jQuery || root.Zepto || root.ender || root.$);
|
||||
}
|
||||
|
||||
})(function(root, Backbone, _, $) {
|
||||
@@ -44,7 +44,7 @@
|
||||
var slice = Array.prototype.slice;
|
||||
|
||||
// Current version of the library. Keep in sync with `package.json`.
|
||||
Backbone.VERSION = '1.3.3';
|
||||
Backbone.VERSION = '1.4.0';
|
||||
|
||||
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
|
||||
// the `$` variable.
|
||||
@@ -68,54 +68,6 @@
|
||||
// form param named `model`.
|
||||
Backbone.emulateJSON = false;
|
||||
|
||||
// Proxy Backbone class methods to Underscore functions, wrapping the model's
|
||||
// `attributes` object or collection's `models` array behind the scenes.
|
||||
//
|
||||
// collection.filter(function(model) { return model.get('age') > 10 });
|
||||
// collection.each(this.addView);
|
||||
//
|
||||
// `Function#apply` can be slow so we use the method's arg count, if we know it.
|
||||
var addMethod = function(length, method, attribute) {
|
||||
switch (length) {
|
||||
case 1: return function() {
|
||||
return _[method](this[attribute]);
|
||||
};
|
||||
case 2: return function(value) {
|
||||
return _[method](this[attribute], value);
|
||||
};
|
||||
case 3: return function(iteratee, context) {
|
||||
return _[method](this[attribute], cb(iteratee, this), context);
|
||||
};
|
||||
case 4: return function(iteratee, defaultVal, context) {
|
||||
return _[method](this[attribute], cb(iteratee, this), defaultVal, context);
|
||||
};
|
||||
default: return function() {
|
||||
var args = slice.call(arguments);
|
||||
args.unshift(this[attribute]);
|
||||
return _[method].apply(_, args);
|
||||
};
|
||||
}
|
||||
};
|
||||
var addUnderscoreMethods = function(Class, methods, attribute) {
|
||||
_.each(methods, function(length, method) {
|
||||
if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
|
||||
});
|
||||
};
|
||||
|
||||
// Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
|
||||
var cb = function(iteratee, instance) {
|
||||
if (_.isFunction(iteratee)) return iteratee;
|
||||
if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
|
||||
if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
|
||||
return iteratee;
|
||||
};
|
||||
var modelMatcher = function(attrs) {
|
||||
var matcher = _.matches(attrs);
|
||||
return function(model) {
|
||||
return matcher(model.attributes);
|
||||
};
|
||||
};
|
||||
|
||||
// Backbone.Events
|
||||
// ---------------
|
||||
|
||||
@@ -134,6 +86,9 @@
|
||||
// Regular expression used to split event strings.
|
||||
var eventSplitter = /\s+/;
|
||||
|
||||
// A private global variable to share between listeners and listenees.
|
||||
var _listening;
|
||||
|
||||
// Iterates over the standard `event, callback` (as well as the fancy multiple
|
||||
// space-separated events `"change blur", callback` and jQuery-style event
|
||||
// maps `{event: callback}`).
|
||||
@@ -160,23 +115,21 @@
|
||||
// Bind an event to a `callback` function. Passing `"all"` will bind
|
||||
// the callback to all events fired.
|
||||
Events.on = function(name, callback, context) {
|
||||
return internalOn(this, name, callback, context);
|
||||
};
|
||||
|
||||
// Guard the `listening` argument from the public API.
|
||||
var internalOn = function(obj, name, callback, context, listening) {
|
||||
obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
|
||||
this._events = eventsApi(onApi, this._events || {}, name, callback, {
|
||||
context: context,
|
||||
ctx: obj,
|
||||
listening: listening
|
||||
ctx: this,
|
||||
listening: _listening
|
||||
});
|
||||
|
||||
if (listening) {
|
||||
var listeners = obj._listeners || (obj._listeners = {});
|
||||
listeners[listening.id] = listening;
|
||||
if (_listening) {
|
||||
var listeners = this._listeners || (this._listeners = {});
|
||||
listeners[_listening.id] = _listening;
|
||||
// Allow the listening to use a counter, instead of tracking
|
||||
// callbacks for library interop
|
||||
_listening.interop = false;
|
||||
}
|
||||
|
||||
return obj;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Inversion-of-control versions of `on`. Tell *this* object to listen to
|
||||
@@ -186,17 +139,23 @@
|
||||
if (!obj) return this;
|
||||
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
|
||||
var listeningTo = this._listeningTo || (this._listeningTo = {});
|
||||
var listening = listeningTo[id];
|
||||
var listening = _listening = listeningTo[id];
|
||||
|
||||
// This object is not listening to any other events on `obj` yet.
|
||||
// Setup the necessary references to track the listening callbacks.
|
||||
if (!listening) {
|
||||
var thisId = this._listenId || (this._listenId = _.uniqueId('l'));
|
||||
listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0};
|
||||
this._listenId || (this._listenId = _.uniqueId('l'));
|
||||
listening = _listening = listeningTo[id] = new Listening(this, obj);
|
||||
}
|
||||
|
||||
// Bind callbacks on obj, and keep track of them on listening.
|
||||
internalOn(obj, name, callback, this, listening);
|
||||
// Bind callbacks on obj.
|
||||
var error = tryCatchOn(obj, name, callback, this);
|
||||
_listening = void 0;
|
||||
|
||||
if (error) throw error;
|
||||
// If the target obj is not Backbone.Events, track events manually.
|
||||
if (listening.interop) listening.on(name, callback);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -212,6 +171,16 @@
|
||||
return events;
|
||||
};
|
||||
|
||||
// An try-catch guarded #on function, to prevent poisoning the global
|
||||
// `_listening` variable.
|
||||
var tryCatchOn = function(obj, name, callback, context) {
|
||||
try {
|
||||
obj.on(name, callback, context);
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
// Remove one or many callbacks. If `context` is null, removes all
|
||||
// callbacks with that function. If `callback` is null, removes all
|
||||
// callbacks for the event. If `name` is null, removes all bound
|
||||
@@ -222,6 +191,7 @@
|
||||
context: context,
|
||||
listeners: this._listeners
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -232,7 +202,6 @@
|
||||
if (!listeningTo) return this;
|
||||
|
||||
var ids = obj ? [obj._listenId] : _.keys(listeningTo);
|
||||
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var listening = listeningTo[ids[i]];
|
||||
|
||||
@@ -241,7 +210,9 @@
|
||||
if (!listening) break;
|
||||
|
||||
listening.obj.off(name, callback, this);
|
||||
if (listening.interop) listening.off(name, callback);
|
||||
}
|
||||
if (_.isEmpty(listeningTo)) this._listeningTo = void 0;
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -250,21 +221,18 @@
|
||||
var offApi = function(events, name, callback, options) {
|
||||
if (!events) return;
|
||||
|
||||
var i = 0, listening;
|
||||
var context = options.context, listeners = options.listeners;
|
||||
var i = 0, names;
|
||||
|
||||
// Delete all events listeners and "drop" events.
|
||||
if (!name && !callback && !context) {
|
||||
var ids = _.keys(listeners);
|
||||
for (; i < ids.length; i++) {
|
||||
listening = listeners[ids[i]];
|
||||
delete listeners[listening.id];
|
||||
delete listening.listeningTo[listening.objId];
|
||||
// Delete all event listeners and "drop" events.
|
||||
if (!name && !context && !callback) {
|
||||
for (names = _.keys(listeners); i < names.length; i++) {
|
||||
listeners[names[i]].cleanup();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var names = name ? [name] : _.keys(events);
|
||||
names = name ? [name] : _.keys(events);
|
||||
for (; i < names.length; i++) {
|
||||
name = names[i];
|
||||
var handlers = events[name];
|
||||
@@ -272,7 +240,7 @@
|
||||
// Bail out if there are no events stored.
|
||||
if (!handlers) break;
|
||||
|
||||
// Replace events if there are any remaining. Otherwise, clean up.
|
||||
// Find any remaining events.
|
||||
var remaining = [];
|
||||
for (var j = 0; j < handlers.length; j++) {
|
||||
var handler = handlers[j];
|
||||
@@ -283,21 +251,19 @@
|
||||
) {
|
||||
remaining.push(handler);
|
||||
} else {
|
||||
listening = handler.listening;
|
||||
if (listening && --listening.count === 0) {
|
||||
delete listeners[listening.id];
|
||||
delete listening.listeningTo[listening.objId];
|
||||
}
|
||||
var listening = handler.listening;
|
||||
if (listening) listening.off(name, callback);
|
||||
}
|
||||
}
|
||||
|
||||
// Update tail event if the list has any events. Otherwise, clean up.
|
||||
// Replace events if there are any remaining. Otherwise, clean up.
|
||||
if (remaining.length) {
|
||||
events[name] = remaining;
|
||||
} else {
|
||||
delete events[name];
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
};
|
||||
|
||||
@@ -307,7 +273,7 @@
|
||||
// once for each event, not once for a combination of all events.
|
||||
Events.once = function(name, callback, context) {
|
||||
// Map the event into a `{event: once}` object.
|
||||
var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
|
||||
var events = eventsApi(onceMap, {}, name, callback, this.off.bind(this));
|
||||
if (typeof name === 'string' && context == null) callback = void 0;
|
||||
return this.on(events, callback, context);
|
||||
};
|
||||
@@ -315,7 +281,7 @@
|
||||
// Inversion-of-control versions of `once`.
|
||||
Events.listenToOnce = function(obj, name, callback) {
|
||||
// Map the event into a `{event: once}` object.
|
||||
var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj));
|
||||
var events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj));
|
||||
return this.listenTo(obj, events);
|
||||
};
|
||||
|
||||
@@ -373,6 +339,44 @@
|
||||
}
|
||||
};
|
||||
|
||||
// A listening class that tracks and cleans up memory bindings
|
||||
// when all callbacks have been offed.
|
||||
var Listening = function(listener, obj) {
|
||||
this.id = listener._listenId;
|
||||
this.listener = listener;
|
||||
this.obj = obj;
|
||||
this.interop = true;
|
||||
this.count = 0;
|
||||
this._events = void 0;
|
||||
};
|
||||
|
||||
Listening.prototype.on = Events.on;
|
||||
|
||||
// Offs a callback (or several).
|
||||
// Uses an optimized counter if the listenee uses Backbone.Events.
|
||||
// Otherwise, falls back to manual tracking to support events
|
||||
// library interop.
|
||||
Listening.prototype.off = function(name, callback) {
|
||||
var cleanup;
|
||||
if (this.interop) {
|
||||
this._events = eventsApi(offApi, this._events, name, callback, {
|
||||
context: void 0,
|
||||
listeners: void 0
|
||||
});
|
||||
cleanup = !this._events;
|
||||
} else {
|
||||
this.count--;
|
||||
cleanup = this.count === 0;
|
||||
}
|
||||
if (cleanup) this.cleanup();
|
||||
};
|
||||
|
||||
// Cleans up memory bindings between the listener and the listenee.
|
||||
Listening.prototype.cleanup = function() {
|
||||
delete this.listener._listeningTo[this.obj._listenId];
|
||||
if (!this.interop) delete this.obj._listeners[this.id];
|
||||
};
|
||||
|
||||
// Aliases for backwards compatibility.
|
||||
Events.bind = Events.on;
|
||||
Events.unbind = Events.off;
|
||||
@@ -394,6 +398,7 @@
|
||||
var Model = Backbone.Model = function(attributes, options) {
|
||||
var attrs = attributes || {};
|
||||
options || (options = {});
|
||||
this.preinitialize.apply(this, arguments);
|
||||
this.cid = _.uniqueId(this.cidPrefix);
|
||||
this.attributes = {};
|
||||
if (options.collection) this.collection = options.collection;
|
||||
@@ -422,6 +427,10 @@
|
||||
// You may want to override this if you're experiencing name clashes with model ids.
|
||||
cidPrefix: 'c',
|
||||
|
||||
// preinitialize is an empty function by default. You can override it with a function
|
||||
// or object. preinitialize will run before any instantiation logic is run in the Model.
|
||||
preinitialize: function(){},
|
||||
|
||||
// Initialize is an empty function by default. Override it with your own
|
||||
// initialization logic.
|
||||
initialize: function(){},
|
||||
@@ -562,12 +571,14 @@
|
||||
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
|
||||
var old = this._changing ? this._previousAttributes : this.attributes;
|
||||
var changed = {};
|
||||
var hasChanged;
|
||||
for (var attr in diff) {
|
||||
var val = diff[attr];
|
||||
if (_.isEqual(old[attr], val)) continue;
|
||||
changed[attr] = val;
|
||||
hasChanged = true;
|
||||
}
|
||||
return _.size(changed) ? changed : false;
|
||||
return hasChanged ? changed : false;
|
||||
},
|
||||
|
||||
// Get the previous value of an attribute, recorded at the time the last
|
||||
@@ -643,7 +654,7 @@
|
||||
// Set temporary attributes if `{wait: true}` to properly find new ids.
|
||||
if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
|
||||
|
||||
var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
|
||||
var method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update';
|
||||
if (method === 'patch' && !options.attrs) options.attrs = attrs;
|
||||
var xhr = this.sync(method, this, options);
|
||||
|
||||
@@ -731,14 +742,6 @@
|
||||
|
||||
});
|
||||
|
||||
// Underscore methods that we want to implement on the Model, mapped to the
|
||||
// number of arguments they take.
|
||||
var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
|
||||
omit: 0, chain: 1, isEmpty: 1};
|
||||
|
||||
// Mix in each Underscore method as a proxy to `Model#attributes`.
|
||||
addUnderscoreMethods(Model, modelMethods, 'attributes');
|
||||
|
||||
// Backbone.Collection
|
||||
// -------------------
|
||||
|
||||
@@ -754,6 +757,7 @@
|
||||
// its models in sort order, as they're added and removed.
|
||||
var Collection = Backbone.Collection = function(models, options) {
|
||||
options || (options = {});
|
||||
this.preinitialize.apply(this, arguments);
|
||||
if (options.model) this.model = options.model;
|
||||
if (options.comparator !== void 0) this.comparator = options.comparator;
|
||||
this._reset();
|
||||
@@ -783,6 +787,11 @@
|
||||
// This should be overridden in most cases.
|
||||
model: Model,
|
||||
|
||||
|
||||
// preinitialize is an empty function by default. You can override it with a function
|
||||
// or object. preinitialize will run before any instantiation logic is run in the Collection.
|
||||
preinitialize: function(){},
|
||||
|
||||
// Initialize is an empty function by default. Override it with your own
|
||||
// initialization logic.
|
||||
initialize: function(){},
|
||||
@@ -985,7 +994,7 @@
|
||||
get: function(obj) {
|
||||
if (obj == null) return void 0;
|
||||
return this._byId[obj] ||
|
||||
this._byId[this.modelId(obj.attributes || obj)] ||
|
||||
this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj)] ||
|
||||
obj.cid && this._byId[obj.cid];
|
||||
},
|
||||
|
||||
@@ -1021,7 +1030,7 @@
|
||||
options || (options = {});
|
||||
|
||||
var length = comparator.length;
|
||||
if (_.isFunction(comparator)) comparator = _.bind(comparator, this);
|
||||
if (_.isFunction(comparator)) comparator = comparator.bind(this);
|
||||
|
||||
// Run sort based on type of `comparator`.
|
||||
if (length === 1 || _.isString(comparator)) {
|
||||
@@ -1093,6 +1102,21 @@
|
||||
return attrs[this.model.prototype.idAttribute || 'id'];
|
||||
},
|
||||
|
||||
// Get an iterator of all models in this collection.
|
||||
values: function() {
|
||||
return new CollectionIterator(this, ITERATOR_VALUES);
|
||||
},
|
||||
|
||||
// Get an iterator of all model IDs in this collection.
|
||||
keys: function() {
|
||||
return new CollectionIterator(this, ITERATOR_KEYS);
|
||||
},
|
||||
|
||||
// Get an iterator of all [ID, model] tuples in this collection.
|
||||
entries: function() {
|
||||
return new CollectionIterator(this, ITERATOR_KEYSVALUES);
|
||||
},
|
||||
|
||||
// Private method to reset all internal state. Called when the collection
|
||||
// is first initialized or reset.
|
||||
_reset: function() {
|
||||
@@ -1189,20 +1213,71 @@
|
||||
|
||||
});
|
||||
|
||||
// Underscore methods that we want to implement on the Collection.
|
||||
// 90% of the core usefulness of Backbone Collections is actually implemented
|
||||
// right here:
|
||||
var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0,
|
||||
foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3,
|
||||
select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
|
||||
contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
|
||||
head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
|
||||
without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
|
||||
isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
|
||||
sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3};
|
||||
// Defining an @@iterator method implements JavaScript's Iterable protocol.
|
||||
// In modern ES2015 browsers, this value is found at Symbol.iterator.
|
||||
/* global Symbol */
|
||||
var $$iterator = typeof Symbol === 'function' && Symbol.iterator;
|
||||
if ($$iterator) {
|
||||
Collection.prototype[$$iterator] = Collection.prototype.values;
|
||||
}
|
||||
|
||||
// Mix in each Underscore method as a proxy to `Collection#models`.
|
||||
addUnderscoreMethods(Collection, collectionMethods, 'models');
|
||||
// CollectionIterator
|
||||
// ------------------
|
||||
|
||||
// A CollectionIterator implements JavaScript's Iterator protocol, allowing the
|
||||
// use of `for of` loops in modern browsers and interoperation between
|
||||
// Backbone.Collection and other JavaScript functions and third-party libraries
|
||||
// which can operate on Iterables.
|
||||
var CollectionIterator = function(collection, kind) {
|
||||
this._collection = collection;
|
||||
this._kind = kind;
|
||||
this._index = 0;
|
||||
};
|
||||
|
||||
// This "enum" defines the three possible kinds of values which can be emitted
|
||||
// by a CollectionIterator that correspond to the values(), keys() and entries()
|
||||
// methods on Collection, respectively.
|
||||
var ITERATOR_VALUES = 1;
|
||||
var ITERATOR_KEYS = 2;
|
||||
var ITERATOR_KEYSVALUES = 3;
|
||||
|
||||
// All Iterators should themselves be Iterable.
|
||||
if ($$iterator) {
|
||||
CollectionIterator.prototype[$$iterator] = function() {
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
CollectionIterator.prototype.next = function() {
|
||||
if (this._collection) {
|
||||
|
||||
// Only continue iterating if the iterated collection is long enough.
|
||||
if (this._index < this._collection.length) {
|
||||
var model = this._collection.at(this._index);
|
||||
this._index++;
|
||||
|
||||
// Construct a value depending on what kind of values should be iterated.
|
||||
var value;
|
||||
if (this._kind === ITERATOR_VALUES) {
|
||||
value = model;
|
||||
} else {
|
||||
var id = this._collection.modelId(model.attributes);
|
||||
if (this._kind === ITERATOR_KEYS) {
|
||||
value = id;
|
||||
} else { // ITERATOR_KEYSVALUES
|
||||
value = [id, model];
|
||||
}
|
||||
}
|
||||
return {value: value, done: false};
|
||||
}
|
||||
|
||||
// Once exhausted, remove the reference to the collection so future
|
||||
// calls to the next method always return done.
|
||||
this._collection = void 0;
|
||||
}
|
||||
|
||||
return {value: void 0, done: true};
|
||||
};
|
||||
|
||||
// Backbone.View
|
||||
// -------------
|
||||
@@ -1219,6 +1294,7 @@
|
||||
// if an existing element is not provided...
|
||||
var View = Backbone.View = function(options) {
|
||||
this.cid = _.uniqueId('view');
|
||||
this.preinitialize.apply(this, arguments);
|
||||
_.extend(this, _.pick(options, viewOptions));
|
||||
this._ensureElement();
|
||||
this.initialize.apply(this, arguments);
|
||||
@@ -1242,6 +1318,10 @@
|
||||
return this.$el.find(selector);
|
||||
},
|
||||
|
||||
// preinitialize is an empty function by default. You can override it with a function
|
||||
// or object. preinitialize will run before any instantiation logic is run in the View
|
||||
preinitialize: function(){},
|
||||
|
||||
// Initialize is an empty function by default. Override it with your own
|
||||
// initialization logic.
|
||||
initialize: function(){},
|
||||
@@ -1309,7 +1389,7 @@
|
||||
if (!_.isFunction(method)) method = this[method];
|
||||
if (!method) continue;
|
||||
var match = key.match(delegateEventSplitter);
|
||||
this.delegate(match[1], match[2], _.bind(method, this));
|
||||
this.delegate(match[1], match[2], method.bind(this));
|
||||
}
|
||||
return this;
|
||||
},
|
||||
@@ -1367,6 +1447,94 @@
|
||||
|
||||
});
|
||||
|
||||
// Proxy Backbone class methods to Underscore functions, wrapping the model's
|
||||
// `attributes` object or collection's `models` array behind the scenes.
|
||||
//
|
||||
// collection.filter(function(model) { return model.get('age') > 10 });
|
||||
// collection.each(this.addView);
|
||||
//
|
||||
// `Function#apply` can be slow so we use the method's arg count, if we know it.
|
||||
var addMethod = function(base, length, method, attribute) {
|
||||
switch (length) {
|
||||
case 1: return function() {
|
||||
return base[method](this[attribute]);
|
||||
};
|
||||
case 2: return function(value) {
|
||||
return base[method](this[attribute], value);
|
||||
};
|
||||
case 3: return function(iteratee, context) {
|
||||
return base[method](this[attribute], cb(iteratee, this), context);
|
||||
};
|
||||
case 4: return function(iteratee, defaultVal, context) {
|
||||
return base[method](this[attribute], cb(iteratee, this), defaultVal, context);
|
||||
};
|
||||
default: return function() {
|
||||
var args = slice.call(arguments);
|
||||
args.unshift(this[attribute]);
|
||||
return base[method].apply(base, args);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var addUnderscoreMethods = function(Class, base, methods, attribute) {
|
||||
_.each(methods, function(length, method) {
|
||||
if (base[method]) Class.prototype[method] = addMethod(base, length, method, attribute);
|
||||
});
|
||||
};
|
||||
|
||||
// Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
|
||||
var cb = function(iteratee, instance) {
|
||||
if (_.isFunction(iteratee)) return iteratee;
|
||||
if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
|
||||
if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
|
||||
return iteratee;
|
||||
};
|
||||
var modelMatcher = function(attrs) {
|
||||
var matcher = _.matches(attrs);
|
||||
return function(model) {
|
||||
return matcher(model.attributes);
|
||||
};
|
||||
};
|
||||
|
||||
// Underscore methods that we want to implement on the Collection.
|
||||
// 90% of the core usefulness of Backbone Collections is actually implemented
|
||||
// right here:
|
||||
var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0,
|
||||
foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3,
|
||||
select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
|
||||
contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
|
||||
head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
|
||||
without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
|
||||
isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
|
||||
sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3};
|
||||
|
||||
|
||||
// Underscore methods that we want to implement on the Model, mapped to the
|
||||
// number of arguments they take.
|
||||
var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
|
||||
omit: 0, chain: 1, isEmpty: 1};
|
||||
|
||||
// Mix in each Underscore method as a proxy to `Collection#models`.
|
||||
|
||||
_.each([
|
||||
[Collection, collectionMethods, 'models'],
|
||||
[Model, modelMethods, 'attributes']
|
||||
], function(config) {
|
||||
var Base = config[0],
|
||||
methods = config[1],
|
||||
attribute = config[2];
|
||||
|
||||
Base.mixin = function(obj) {
|
||||
var mappings = _.reduce(_.functions(obj), function(memo, name) {
|
||||
memo[name] = 0;
|
||||
return memo;
|
||||
}, {});
|
||||
addUnderscoreMethods(Base, obj, mappings, attribute);
|
||||
};
|
||||
|
||||
addUnderscoreMethods(Base, _, methods, attribute);
|
||||
});
|
||||
|
||||
// Backbone.sync
|
||||
// -------------
|
||||
|
||||
@@ -1447,11 +1615,11 @@
|
||||
|
||||
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
|
||||
var methodMap = {
|
||||
'create': 'POST',
|
||||
'update': 'PUT',
|
||||
'patch': 'PATCH',
|
||||
'delete': 'DELETE',
|
||||
'read': 'GET'
|
||||
create: 'POST',
|
||||
update: 'PUT',
|
||||
patch: 'PATCH',
|
||||
delete: 'DELETE',
|
||||
read: 'GET'
|
||||
};
|
||||
|
||||
// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
|
||||
@@ -1467,6 +1635,7 @@
|
||||
// matched. Creating a new one sets its `routes` hash, if not set statically.
|
||||
var Router = Backbone.Router = function(options) {
|
||||
options || (options = {});
|
||||
this.preinitialize.apply(this, arguments);
|
||||
if (options.routes) this.routes = options.routes;
|
||||
this._bindRoutes();
|
||||
this.initialize.apply(this, arguments);
|
||||
@@ -1482,6 +1651,10 @@
|
||||
// Set up all inheritable **Backbone.Router** properties and methods.
|
||||
_.extend(Router.prototype, Events, {
|
||||
|
||||
// preinitialize is an empty function by default. You can override it with a function
|
||||
// or object. preinitialize will run before any instantiation logic is run in the Router.
|
||||
preinitialize: function(){},
|
||||
|
||||
// Initialize is an empty function by default. Override it with your own
|
||||
// initialization logic.
|
||||
initialize: function(){},
|
||||
@@ -1539,11 +1712,11 @@
|
||||
// against the current location hash.
|
||||
_routeToRegExp: function(route) {
|
||||
route = route.replace(escapeRegExp, '\\$&')
|
||||
.replace(optionalParam, '(?:$1)?')
|
||||
.replace(namedParam, function(match, optional) {
|
||||
return optional ? match : '([^/?]+)';
|
||||
})
|
||||
.replace(splatParam, '([^?]*?)');
|
||||
.replace(optionalParam, '(?:$1)?')
|
||||
.replace(namedParam, function(match, optional) {
|
||||
return optional ? match : '([^/?]+)';
|
||||
})
|
||||
.replace(splatParam, '([^?]*?)');
|
||||
return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
|
||||
},
|
||||
|
||||
@@ -1571,7 +1744,7 @@
|
||||
// falls back to polling.
|
||||
var History = Backbone.History = function() {
|
||||
this.handlers = [];
|
||||
this.checkUrl = _.bind(this.checkUrl, this);
|
||||
this.checkUrl = this.checkUrl.bind(this);
|
||||
|
||||
// Ensure that `History` can be used outside of the browser.
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -1812,11 +1985,14 @@
|
||||
}
|
||||
var url = rootPath + fragment;
|
||||
|
||||
// Strip the hash and decode for matching.
|
||||
fragment = this.decodeFragment(fragment.replace(pathStripper, ''));
|
||||
// Strip the fragment of the query and hash for matching.
|
||||
fragment = fragment.replace(pathStripper, '');
|
||||
|
||||
if (this.fragment === fragment) return;
|
||||
this.fragment = fragment;
|
||||
// Decode for matching.
|
||||
var decodedFragment = this.decodeFragment(fragment);
|
||||
|
||||
if (this.fragment === decodedFragment) return;
|
||||
this.fragment = decodedFragment;
|
||||
|
||||
// If pushState is available, we use it to set the fragment as a real URL.
|
||||
if (this._usePushState) {
|
||||
|
||||
1868
docs/backbone.html
1868
docs/backbone.html
File diff suppressed because it is too large
Load Diff
243
index.html
243
index.html
@@ -303,7 +303,7 @@
|
||||
<div id="sidebar" class="interface">
|
||||
|
||||
<a class="toc_title" href="#">
|
||||
Backbone.js <span class="version">(1.3.3)</span>
|
||||
Backbone.js <span class="version">(1.4.0)</span>
|
||||
</a>
|
||||
<ul class="toc_section">
|
||||
<li>» <a href="http://github.com/jashkenas/backbone">GitHub Repository</a></li>
|
||||
@@ -348,6 +348,7 @@
|
||||
</a>
|
||||
<ul class="toc_section">
|
||||
<li data-name="extend">– <a href="#Model-extend">extend</a></li>
|
||||
<li data-name="preinitialize">– <a href="#Model-preinitialize">preinitialize</a></li>
|
||||
<li data-name="constructor / initialize">– <a href="#Model-constructor">constructor / initialize</a></li>
|
||||
<li data-name="get">– <a href="#Model-get">get</a></li>
|
||||
<li data-name="set">– <a href="#Model-set">set</a></li>
|
||||
@@ -390,6 +391,7 @@
|
||||
<li data-name="extend">– <a href="#Collection-extend">extend</a></li>
|
||||
<li data-name="model">– <a href="#Collection-model">model</a></li>
|
||||
<li data-name="modelId">– <a href="#Collection-modelId">modelId</a></li>
|
||||
<li data-name="preinitialize" data-name="preinitialize">– <a href="#Collection-preinitialize">preinitialize</a></li>
|
||||
<li data-name="constructor / initialize" data-name="constructor / initialize">– <a href="#Collection-constructor">constructor / initialize</a></li>
|
||||
<li data-name="models">– <a href="#Collection-models">models</a></li>
|
||||
<li data-name="toJSON">– <a href="#Collection-toJSON">toJSON</a></li>
|
||||
@@ -417,6 +419,7 @@
|
||||
<li data-name="clone">– <a href="#Collection-clone">clone</a></li>
|
||||
<li data-name="fetch">– <a href="#Collection-fetch">fetch</a></li>
|
||||
<li data-name="create">– <a href="#Collection-create">create</a></li>
|
||||
<li data-name="sync">– <a href="#Collection-mixin">mixin</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -427,6 +430,7 @@
|
||||
<ul class="toc_section">
|
||||
<li data-name="extend">– <a href="#Router-extend">extend</a></li>
|
||||
<li data-name="routes">– <a href="#Router-routes">routes</a></li>
|
||||
<li data-name="preinitialize">– <a href="#Router-preinitialize">preinitialize</a></li>
|
||||
<li data-name="constructor / initialize">– <a href="#Router-constructor">constructor / initialize</a></li>
|
||||
<li data-name="route">– <a href="#Router-route">route</a></li>
|
||||
<li data-name="navigate">– <a href="#Router-navigate">navigate</a></li>
|
||||
@@ -461,6 +465,7 @@
|
||||
</a>
|
||||
<ul class="toc_section">
|
||||
<li data-name="extend">– <a href="#View-extend">extend</a></li>
|
||||
<li data-name="preinitialize">– <a href="#View-preinitialize">preinitialize</a></li>
|
||||
<li data-name="constructor / initialize">– <a href="#View-constructor">constructor / initialize</a></li>
|
||||
<li data-name="el">– <a href="#View-el">el</a></li>
|
||||
<li data-name="$el">– <a href="#View-$el">$el</a></li>
|
||||
@@ -588,10 +593,7 @@
|
||||
<p>
|
||||
You can report bugs and discuss features on the
|
||||
<a href="http://github.com/jashkenas/backbone/issues">GitHub issues page</a>,
|
||||
on Freenode IRC in the <tt>#documentcloud</tt> channel, post questions to the
|
||||
<a href="https://groups.google.com/forum/#!forum/backbonejs">Google Group</a>,
|
||||
add pages to the <a href="https://github.com/jashkenas/backbone/wiki">wiki</a>
|
||||
or send tweets to <a href="http://twitter.com/documentcloud">@documentcloud</a>.
|
||||
or add pages to the <a href="https://github.com/jashkenas/backbone/wiki">wiki</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@@ -614,7 +616,7 @@
|
||||
<tr>
|
||||
<td><a class="punch" href="backbone-min.js">Production Version (1.3.3)</a></td>
|
||||
<td class="text" style="line-height: 16px;">
|
||||
<i>7.6kb, Packed and gzipped</i><br />
|
||||
<i>7.9kb, Packed and gzipped</i><br />
|
||||
<small>(<a href="backbone-min.map">Source Map</a>)</small>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -622,9 +624,6 @@
|
||||
<td><a class="punch" href="https://raw.github.com/jashkenas/backbone/master/backbone.js">Edge Version (master)</a></td>
|
||||
<td>
|
||||
<i>Unreleased, use at your own risk</i>
|
||||
<a class="travis-badge" href="https://travis-ci.org/jashkenas/backbone">
|
||||
<img src="https://travis-ci.org/jashkenas/backbone.png" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -633,9 +632,7 @@
|
||||
Backbone's only hard dependency is
|
||||
<a href="http://underscorejs.org/">Underscore.js</a> <small>( >= 1.8.3)</small>.
|
||||
For RESTful persistence and DOM manipulation with <a href="#View">Backbone.View</a>,
|
||||
include <b><a href="https://jquery.com/">jQuery</a></b> ( >= 1.11.0), and
|
||||
<b><a href="https://github.com/douglascrockford/JSON-js">json2.js</a></b> for older
|
||||
Internet Explorer support.
|
||||
include <b><a href="https://jquery.com/">jQuery</a></b> ( >= 1.11.0).
|
||||
<i>(Mimics of the Underscore and jQuery APIs, such as
|
||||
<a href="https://lodash.com/">Lodash</a> and
|
||||
<a href="http://zeptojs.com/">Zepto</a>, will
|
||||
@@ -704,7 +701,7 @@
|
||||
<b>Model</b>
|
||||
<ul>
|
||||
<li>Orchestrates data and business logic.</li>
|
||||
<li>Loads and saves from the server.</li>
|
||||
<li>Loads and saves data from the server.</li>
|
||||
<li>Emits events when data changes.</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1031,7 +1028,7 @@ view.stopListening(model);
|
||||
<ul class="small">
|
||||
<li><b>"add"</b> (model, collection, options) — when a model is added to a collection.</li>
|
||||
<li><b>"remove"</b> (model, collection, options) — when a model is removed from a collection.</li>
|
||||
<li><b>"update"</b> (collection, options) — single event triggered after any number of models have been added or removed from a collection.</li>
|
||||
<li><b>"update"</b> (collection, options) — single event triggered after any number of models have been added, removed or changed in a collection.</li>
|
||||
<li><b>"reset"</b> (collection, options) — when the collection's entire contents have been <a href="#Collection-reset">reset</a>.</li>
|
||||
<li><b>"sort"</b> (collection, options) — when the collection has been re-sorted.</li>
|
||||
<li><b>"change"</b> (model, options) — when a model's attributes have changed.</li>
|
||||
@@ -1039,7 +1036,7 @@ view.stopListening(model);
|
||||
<li><b>"destroy"</b> (model, collection, options) — when a model is <a href="#Model-destroy">destroyed</a>.</li>
|
||||
<li><b>"request"</b> (model_or_collection, xhr, options) — when a model or collection has started a request to the server.</li>
|
||||
<li><b>"sync"</b> (model_or_collection, response, options) — when a model or collection has been successfully synced with the server.</li>
|
||||
<li><b>"error"</b> (model_or_collection, response, options) — when a model's or collection's request to the server has failed.</li>
|
||||
<li><b>"error"</b> (model_or_collection, xhr, options) — when a model's or collection's request to the server has failed.</li>
|
||||
<li><b>"invalid"</b> (model, error, options) — when a model's <a href="#Model-validate">validation</a> fails on the client.</li>
|
||||
<li><b>"route:[name]"</b> (params) — Fired by the router when a specific route is matched.</li>
|
||||
<li><b>"route"</b> (route, params) — Fired by the router when <i>any</i> route has been matched.</li>
|
||||
@@ -1146,6 +1143,24 @@ var Note = Backbone.Model.extend({
|
||||
...
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
<p id="Model-preinitialize">
|
||||
<b class="header">preinitialize</b><code>new Model([attributes], [options])</code>
|
||||
<br />
|
||||
For use with models as ES classes. If you define a <b>preinitialize</b>
|
||||
method, it will be invoked when the Model is first created, before any
|
||||
instantiation logic is run for the Model.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
class Country extends Backbone.Model {
|
||||
preinitialize({countryCode}) {
|
||||
this.name = COUNTRY_NAMES[countryCode];
|
||||
}
|
||||
|
||||
initialize() { ... }
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p id="Model-constructor">
|
||||
@@ -1272,6 +1287,8 @@ if (note.has("title")) {
|
||||
A special property of models, the <b>id</b> is an arbitrary string
|
||||
(integer id or UUID). If you set the <b>id</b> in the
|
||||
attributes hash, it will be copied onto the model as a direct property.
|
||||
<code>model.id</code> should not be manipulated directly,
|
||||
it should be modified only via <code>model.set('id', …)</code>.
|
||||
Models can be retrieved by id from collections, and the id is used to generate
|
||||
model URLs by default.
|
||||
</p>
|
||||
@@ -1531,21 +1548,22 @@ chapters.keys().join(', ');
|
||||
<br />
|
||||
This method is left undefined and you're encouraged to override it with
|
||||
any custom validation logic you have that can be performed in JavaScript.
|
||||
If the attributes are valid, don't return anything from <b>validate</b>;
|
||||
if they are invalid return an error of your choosing. It can be as
|
||||
simple as a string error message to be displayed, or a complete error
|
||||
object that describes the error programmatically.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
By default <tt>save</tt> checks <b>validate</b> before
|
||||
setting any attributes but you may also tell <tt>set</tt> to validate
|
||||
the new attributes by passing <tt>{validate: true}</tt> as an option.
|
||||
<br />
|
||||
The <b>validate</b> method receives the model attributes as well as any
|
||||
options passed to <tt>set</tt> or <tt>save</tt>.
|
||||
If the attributes are valid, don't return anything from <b>validate</b>;
|
||||
if they are invalid return an error of your choosing. It
|
||||
can be as simple as a string error message to be displayed, or a complete
|
||||
error object that describes the error programmatically. If <b>validate</b>
|
||||
returns an error, <tt>save</tt> will not continue, and the
|
||||
model attributes will not be modified on the server.
|
||||
Failed validations trigger an <tt>"invalid"</tt> event, and set the
|
||||
<tt>validationError</tt> property on the model with the value returned by
|
||||
this method.
|
||||
options passed to <tt>set</tt> or <tt>save</tt>, if <b>validate</b>
|
||||
returns an error, <tt>save</tt> does not continue, the model attributes
|
||||
are not modified on the server, an <tt>"invalid"</tt> event is triggered,
|
||||
and the <tt>validationError</tt> property is set on the model with the
|
||||
value returned by this method.
|
||||
</p>
|
||||
|
||||
<pre class="runnable">
|
||||
@@ -1583,11 +1601,18 @@ one.save({
|
||||
</p>
|
||||
|
||||
<p id="Model-isValid">
|
||||
<b class="header">isValid</b><code>model.isValid()</code>
|
||||
<b class="header">isValid</b><code>model.isValid(options)</code>
|
||||
<br />
|
||||
Run <a href="#Model-validate">validate</a> to check the model state.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <tt>validate</tt> method receives the model attributes as well as any
|
||||
options passed to <b>isValid</b>, if <tt>validate</tt> returns an error
|
||||
an <tt>"invalid"</tt> event is triggered, and the error is set on the
|
||||
model in the <tt>validationError</tt> property.
|
||||
</p>
|
||||
|
||||
<pre class="runnable">
|
||||
var Chapter = Backbone.Model.extend({
|
||||
validate: function(attrs, options) {
|
||||
@@ -1771,11 +1796,11 @@ bill.set({name : "Bill Jones"});
|
||||
<p id="Collection-model">
|
||||
<b class="header">model</b><code>collection.model([attrs], [options])</code>
|
||||
<br />
|
||||
Override this property to specify the model class that the collection
|
||||
contains. If defined, you can pass raw attributes objects (and arrays) to
|
||||
Override this property to specify the model class that the collection contains.
|
||||
If defined, you can pass raw attributes objects (and arrays) and options to
|
||||
<a href="#Collection-add">add</a>, <a href="#Collection-create">create</a>,
|
||||
and <a href="#Collection-reset">reset</a>, and the attributes will be
|
||||
converted into a model of the proper type.
|
||||
converted into a model of the proper type using the provided options, if any.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
@@ -1836,6 +1861,24 @@ var library = new Library([
|
||||
var dvdId = library.get('dvd1').id;
|
||||
var vhsId = library.get('vhs1').id;
|
||||
alert('dvd: ' + dvdId + ', vhs: ' + vhsId);
|
||||
</pre>
|
||||
|
||||
<p id="Collection-preinitialize">
|
||||
<b class="header">preinitialize</b><code>new Backbone.Collection([models], [options])</code>
|
||||
<br />
|
||||
For use with collections as ES classes. If you define a <b>preinitialize</b>
|
||||
method, it will be invoked when the Collection is first created and before
|
||||
any instantiation logic is run for the Collection.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
class Library extends Backbone.Collection {
|
||||
preinitialize() {
|
||||
this.on("add", function() {
|
||||
console.log("Add model event got fired!");
|
||||
};
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p id="Collection-constructor">
|
||||
@@ -1966,7 +2009,8 @@ var randomThree = books.sample(3);
|
||||
<br />
|
||||
Add a model (or an array of models) to the collection, firing an <tt>"add"</tt>
|
||||
event for each model, and an <tt>"update"</tt> event afterwards. If a <a href="#Collection-model">model</a> property is defined, you may also pass
|
||||
raw attributes objects, and have them be vivified as instances of the model.
|
||||
raw attributes objects and options, and have them be vivified as instances of the model using
|
||||
the provided options.
|
||||
Returns the added (or preexisting, if duplicate) models.
|
||||
Pass <tt>{at: index}</tt> to splice the model into the collection at the
|
||||
specified <tt>index</tt>. If you're adding models to the collection that are
|
||||
@@ -2128,14 +2172,13 @@ var book = library.get(110);
|
||||
<b class="header">comparator</b><code>collection.comparator</code>
|
||||
<br />
|
||||
By default there is no <b>comparator</b> for a collection.
|
||||
If you define a comparator, it will be used to maintain
|
||||
the collection in sorted order. This means that as models are added,
|
||||
they are inserted at the correct index in <tt>collection.models</tt>.
|
||||
If you define a comparator, it will be used to sort the collection any
|
||||
time a model is added.
|
||||
A comparator can be defined as a
|
||||
<a href="http://underscorejs.org/#sortBy">sortBy</a>
|
||||
(pass a function that takes a single argument),
|
||||
as a
|
||||
<a href="https://developer.mozilla.org/JavaScript/Reference/Global_Objects/Array/sort">sort</a>
|
||||
<a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort">sort</a>
|
||||
(pass a comparator function that expects two arguments),
|
||||
or as a string indicating the attribute to sort by.
|
||||
</p>
|
||||
@@ -2178,9 +2221,9 @@ alert(chapters.pluck('title'));
|
||||
<p id="Collection-sort">
|
||||
<b class="header">sort</b><code>collection.sort([options])</code>
|
||||
<br />
|
||||
Force a collection to re-sort itself. You don't need to call this under
|
||||
normal circumstances, as a collection with a <a href="#Collection-comparator">comparator</a>
|
||||
will sort itself whenever a model is added. To disable sorting when adding
|
||||
Force a collection to re-sort itself. Note that a collection with a
|
||||
<a href="#Collection-comparator">comparator</a> will sort itself
|
||||
automatically whenever a model is added. To disable sorting when adding
|
||||
a model, pass <tt>{sort: false}</tt> to <tt>add</tt>. Calling <b>sort</b>
|
||||
triggers a <tt>"sort"</tt> event on the collection.
|
||||
</p>
|
||||
@@ -2229,6 +2272,7 @@ alert(musketeers.length);
|
||||
<br />
|
||||
Just like <a href="#Collection-where">where</a>, but directly returns only
|
||||
the first model in the collection that matches the passed <b>attributes</b>.
|
||||
If no model matches returns <tt>undefined</tt>.
|
||||
</p>
|
||||
|
||||
<p id="Collection-url">
|
||||
@@ -2341,8 +2385,8 @@ accounts.fetch();
|
||||
failed, the model will be unsaved, with validation errors.
|
||||
In order for this to work, you should set the
|
||||
<a href="#Collection-model">model</a> property of the collection.
|
||||
The <b>create</b> method can accept either an attributes hash or an
|
||||
existing, unsaved model object.
|
||||
The <b>create</b> method can accept either an attributes hash and options to be
|
||||
passed down during model instantiation or an existing, unsaved model object.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@@ -2364,6 +2408,32 @@ var othello = nypl.create({
|
||||
title: "Othello",
|
||||
author: "William Shakespeare"
|
||||
});
|
||||
</pre>
|
||||
|
||||
<p id="Collection-mixin">
|
||||
<b class="header">mixin</b><code>Backbone.Collection.mixin(properties)</code>
|
||||
<br />
|
||||
<code>mixin</code> provides a way to enhance the base <b>Backbone.Collection</b>
|
||||
and any collections which extend it. This can be used to add generic methods
|
||||
(e.g. additional <a href="#Collection-Underscore-Methods"><b>Underscore Methods</b></a>).
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
Backbone.Collection.mixin({
|
||||
sum: function(models, iteratee) {
|
||||
return _.reduce(models, function(s, m) {
|
||||
return s + iteratee(m);
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
var cart = new Backbone.Collection([
|
||||
{price: 16, name: 'monopoly'},
|
||||
{price: 5, name: 'deck of cards'},
|
||||
{price: 20, name: 'chess'}
|
||||
]);
|
||||
|
||||
var cost = cart.sum('price');
|
||||
</pre>
|
||||
|
||||
<h2 id="Router">Backbone.Router</h2>
|
||||
@@ -2481,6 +2551,30 @@ routes: {
|
||||
router.on("route:help", function(page) {
|
||||
...
|
||||
});
|
||||
</pre>
|
||||
|
||||
<p id="Router-preinitialize">
|
||||
<b class="header">preinitialize</b><code>new Backbone.Router([options])</code>
|
||||
<br />
|
||||
For use with routers as ES classes. If you define a <b>preinitialize</b>
|
||||
method, it will be invoked when the Router is first created and before
|
||||
any instantiation logic is run for the Router.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
class Router extends Backbone.Router {
|
||||
preinitialize() {
|
||||
// Override execute method
|
||||
this.execute = function(callback, args, name) {
|
||||
if (!loggedIn) {
|
||||
goToLogin();
|
||||
return false;
|
||||
}
|
||||
args.push(parseQueryString(args.pop()));
|
||||
if (callback) callback.apply(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p id="Router-constructor">
|
||||
@@ -2807,6 +2901,28 @@ var DocumentRow = Backbone.View.extend({
|
||||
you want to wait to define them until runtime.
|
||||
</p>
|
||||
|
||||
<p id="View-preinitialize">
|
||||
<b class="header">preinitialize</b><code>new View([options])</code>
|
||||
<br />
|
||||
For use with views as ES classes. If you define a <b>preinitialize</b>
|
||||
method, it will be invoked when the view is first created, before any
|
||||
instantiation logic is run.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
class Document extends Backbone.View {
|
||||
preinitialize({autoRender}) {
|
||||
this.autoRender = autoRender;
|
||||
}
|
||||
|
||||
initialize() {
|
||||
if (this.autoRender) {
|
||||
this.listenTo(this.model, "change", this.render);
|
||||
}
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p id="View-constructor">
|
||||
<b class="header">constructor / initialize</b><code>new View([options])</code>
|
||||
<br />
|
||||
@@ -3336,7 +3452,7 @@ inbox.messages.fetch({reset: true});
|
||||
</pre>
|
||||
|
||||
<p>You have to <a href="http://mathiasbynens.be/notes/etago">escape</a>
|
||||
<tt></</tt> within the JSON string, to prevent javascript injection
|
||||
<tt></</tt> within the JSON string, to prevent JavaScript injection
|
||||
attacks.
|
||||
|
||||
<p id="FAQ-extending">
|
||||
@@ -3471,9 +3587,9 @@ ActiveRecord::Base.include_root_in_json = false
|
||||
<h2 id="examples">Examples</h2>
|
||||
|
||||
<p>
|
||||
The list of examples that follows, while long, is not exhaustive. If you've
|
||||
worked on an app that uses Backbone, please add it to the
|
||||
<a href="https://github.com/jashkenas/backbone/wiki/Projects-and-Companies-using-Backbone">wiki page of Backbone apps</a>.
|
||||
The list of examples that follows, while long, is not exhaustive — nor in
|
||||
any way current. If you've worked on an app that uses Backbone, please
|
||||
add it to the <a href="https://github.com/jashkenas/backbone/wiki/Projects-and-Companies-using-Backbone">wiki page of Backbone apps</a>.
|
||||
</p>
|
||||
|
||||
<p id="examples-todos">
|
||||
@@ -3595,7 +3711,7 @@ ActiveRecord::Base.include_root_in_json = false
|
||||
<a href="http://earth.nullschool.net">Earth.nullschool.net</a> displays real-time weather
|
||||
conditions on an interactive animated globe, and Backbone provides the
|
||||
foundation upon which all of the site's components are built. Despite the
|
||||
presence of several other javascript libraries, Backbone's non-opinionated
|
||||
presence of several other JavaScript libraries, Backbone's non-opinionated
|
||||
design made it effortless to mix-in the <a href="#Events">Events</a> functionality used for
|
||||
distributing state changes throughout the page. When the decision was made
|
||||
to switch to Backbone, large blocks of custom logic simply disappeared.
|
||||
@@ -3918,7 +4034,7 @@ ActiveRecord::Base.include_root_in_json = false
|
||||
large single-page application that
|
||||
benefits from Backbone's structure and modularity. ZocDoc's Backbone
|
||||
classes are tested with
|
||||
<a href="http://pivotal.github.io/jasmine/">Jasmine</a>, and delivered
|
||||
<a href="https://jasmine.github.io/">Jasmine</a>, and delivered
|
||||
to the end user with
|
||||
<a href="http://getcassette.net/">Cassette</a>.
|
||||
</p>
|
||||
@@ -4056,7 +4172,7 @@ ActiveRecord::Base.include_root_in_json = false
|
||||
<a href="http://m.soundcloud.com">SoundCloud Mobile</a>. The project uses
|
||||
the public SoundCloud <a href="http://soundcloud.com/developers">API</a>
|
||||
as a data source (channeled through a nginx proxy),
|
||||
<a href="http://api.jquery.com/category/plugins/templates/">jQuery templates</a>
|
||||
<a href="https://github.com/BorisMoore/jquery-tmpl">jQuery templates</a>
|
||||
for the rendering, <a href="http://docs.jquery.com/Qunit">Qunit
|
||||
</a> and <a href="http://www.phantomjs.org/">PhantomJS</a> for
|
||||
the testing suite. The JS code, templates and CSS are built for the
|
||||
@@ -4326,6 +4442,32 @@ ActiveRecord::Base.include_root_in_json = false
|
||||
|
||||
<h2 id="changelog">Change Log</h2>
|
||||
|
||||
<b class="header">1.4.0</b> — <small><i>Feb. 19, 2019</i></small>
|
||||
— <a href="https://github.com/jashkenas/backbone/compare/1.3.3...1.4.0">Diff</a>
|
||||
— <a href="https://cdn.rawgit.com/jashkenas/backbone/1.4.0/index.html">Docs</a>
|
||||
<br />
|
||||
<ul style="margin-top: 5px;">
|
||||
<li>
|
||||
Collections now support the <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols">Javascript Iterator Protocol!</a>
|
||||
</li>
|
||||
<li>
|
||||
<tt>listenTo</tt> uses the listened object's public <tt>on</tt> method.
|
||||
This helps maintain interoperability between Backbone and other event
|
||||
libraries (including Node.js).
|
||||
</li>
|
||||
<li>
|
||||
Added support for setting instance properties before the constructor in
|
||||
<tt>ES2015 classes</tt> with a <tt>preinitialize</tt> method.
|
||||
</li>
|
||||
<li>
|
||||
<tt>Collection.get</tt> now checks if obj is a <tt>Model</tt> to allow
|
||||
retrieving models with an `attributes` key.
|
||||
</li>
|
||||
<li>
|
||||
Fixed several issues with Router's URL hashing and parsing.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<b class="header">1.3.3</b> — <small><i>Apr. 5, 2016</i></small>
|
||||
— <a href="https://github.com/jashkenas/backbone/compare/1.2.3...1.3.3">Diff</a>
|
||||
— <a href="https://cdn.rawgit.com/jashkenas/backbone/1.3.3/index.html">Docs</a>
|
||||
@@ -4339,6 +4481,9 @@ ActiveRecord::Base.include_root_in_json = false
|
||||
Added <tt>options.changes</tt> to <tt>Collection</tt> "update" event which
|
||||
includes added, merged, and removed models.
|
||||
</li>
|
||||
<li>
|
||||
Added support for <tt>Collection#mixin</tt> and <tt>Model#mixin</tt>.
|
||||
</li>
|
||||
<li>
|
||||
Ensured <tt>Collection#reduce</tt> and <tt>Collection#reduceRight</tt>
|
||||
work without an initial <tt>accumulator</tt> value.
|
||||
@@ -4419,7 +4564,7 @@ ActiveRecord::Base.include_root_in_json = false
|
||||
Bug fix in <tt>Collection#remove</tt>. The removed models are now actually returned.
|
||||
</li>
|
||||
<li>
|
||||
<tt>Model#fetch</tt> no longer parses the response when passing <tt>patch: false</tt>.
|
||||
<tt>Model#fetch</tt> no longer parses the response when passing <tt>parse: false</tt>.
|
||||
</li>
|
||||
<li>
|
||||
Bug fix for iframe-based History when used with JSDOM.
|
||||
@@ -4518,7 +4663,7 @@ ActiveRecord::Base.include_root_in_json = false
|
||||
have actually been removed from the collection.
|
||||
</li>
|
||||
<li>
|
||||
Fixed loading Backbone.js in strict ES6 module loaders.
|
||||
Fixed loading Backbone.js in strict ES2015 module loaders.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -17,10 +17,6 @@ var sauceBrowsers = _.reduce([
|
||||
['internet explorer', '11', 'Windows 10'],
|
||||
['internet explorer', '10', 'Windows 8'],
|
||||
['internet explorer', '9', 'Windows 7'],
|
||||
['internet explorer', '8'],
|
||||
// Currently karma-sauce has issues with sockets and these browsers
|
||||
// ['internet explorer', '7'],
|
||||
// ['internet explorer', '6'],
|
||||
|
||||
['opera', '12'],
|
||||
['opera', '11'],
|
||||
@@ -30,7 +26,7 @@ var sauceBrowsers = _.reduce([
|
||||
|
||||
// 4.3 currently erros with some router tests
|
||||
// ['android', '4.3'],
|
||||
|
||||
|
||||
['android', '4.0'],
|
||||
|
||||
['safari', '8.0', 'OS X 10.10'],
|
||||
|
||||
11
package.json
11
package.json
@@ -18,11 +18,12 @@
|
||||
"devDependencies": {
|
||||
"coffee-script": "1.7.1",
|
||||
"docco": "0.7.0",
|
||||
"eslint": "1.10.x",
|
||||
"eslint": "^2.11.0",
|
||||
"karma": "^0.13.13",
|
||||
"karma-phantomjs-launcher": "^0.1.4",
|
||||
"karma-qunit": "^0.1.5",
|
||||
"qunitjs": "^1.18.0",
|
||||
"karma-phantomjs-launcher": "^1.0.0",
|
||||
"karma-qunit": "^1.0.0",
|
||||
"phantomjs-prebuilt": "^2.1.7",
|
||||
"qunitjs": "^2.0.0",
|
||||
"uglify-js": "^2.4.17"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -32,7 +33,7 @@
|
||||
"lint": "eslint backbone.js test/*.js"
|
||||
},
|
||||
"main": "backbone.js",
|
||||
"version": "1.3.3",
|
||||
"version": "1.4.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function() {
|
||||
(function(QUnit) {
|
||||
|
||||
var a, b, c, d, e, col, otherCol;
|
||||
|
||||
@@ -593,7 +593,7 @@
|
||||
assert.equal(error, 'fail');
|
||||
assert.equal(options.validationError, 'fail');
|
||||
});
|
||||
assert.equal(collection.create({'foo': 'bar'}, {validate: true}), false);
|
||||
assert.equal(collection.create({foo: 'bar'}, {validate: true}), false);
|
||||
});
|
||||
|
||||
QUnit.test('create will pass extra options to success callback', function(assert) {
|
||||
@@ -661,6 +661,31 @@
|
||||
assert.equal(coll.one, 1);
|
||||
});
|
||||
|
||||
QUnit.test('preinitialize', function(assert) {
|
||||
assert.expect(1);
|
||||
var Collection = Backbone.Collection.extend({
|
||||
preinitialize: function() {
|
||||
this.one = 1;
|
||||
}
|
||||
});
|
||||
var coll = new Collection;
|
||||
assert.equal(coll.one, 1);
|
||||
});
|
||||
|
||||
QUnit.test('preinitialize occurs before the collection is set up', function(assert) {
|
||||
assert.expect(2);
|
||||
var Collection = Backbone.Collection.extend({
|
||||
preinitialize: function() {
|
||||
assert.notEqual(this.model, FooModel);
|
||||
}
|
||||
});
|
||||
var FooModel = Backbone.Model.extend({id: 'foo'});
|
||||
var coll = new Collection({}, {
|
||||
model: FooModel
|
||||
});
|
||||
assert.equal(coll.model, FooModel);
|
||||
});
|
||||
|
||||
QUnit.test('toJSON', function(assert) {
|
||||
assert.expect(1);
|
||||
assert.equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
|
||||
@@ -686,6 +711,27 @@
|
||||
assert.equal(coll.findWhere({a: 4}), void 0);
|
||||
});
|
||||
|
||||
QUnit.test('mixin', function(assert) {
|
||||
Backbone.Collection.mixin({
|
||||
sum: function(models, iteratee) {
|
||||
return _.reduce(models, function(s, m) {
|
||||
return s + iteratee(m);
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
var coll = new Backbone.Collection([
|
||||
{a: 1},
|
||||
{a: 1, b: 2},
|
||||
{a: 2, b: 2},
|
||||
{a: 3}
|
||||
]);
|
||||
|
||||
assert.equal(coll.sum(function(m) {
|
||||
return m.get('a');
|
||||
}), 7);
|
||||
});
|
||||
|
||||
QUnit.test('Underscore methods', function(assert) {
|
||||
assert.expect(21);
|
||||
assert.equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
|
||||
@@ -1724,15 +1770,69 @@
|
||||
return new M(attrs);
|
||||
}
|
||||
});
|
||||
var c2 = new C2({'_id': 1});
|
||||
var c2 = new C2({_id: 1});
|
||||
assert.equal(c2.get(1), void 0);
|
||||
assert.equal(c2.modelId(c2.at(0).attributes), void 0);
|
||||
var m = new M({'_id': 2});
|
||||
var m = new M({_id: 2});
|
||||
c2.add(m);
|
||||
assert.equal(c2.get(2), void 0);
|
||||
assert.equal(c2.modelId(m.attributes), void 0);
|
||||
});
|
||||
|
||||
QUnit.test('Collection implements Iterable, values is default iterator function', function(assert) {
|
||||
/* global Symbol */
|
||||
var $$iterator = typeof Symbol === 'function' && Symbol.iterator;
|
||||
// This test only applies to environments which define Symbol.iterator.
|
||||
if (!$$iterator) {
|
||||
assert.expect(0);
|
||||
return;
|
||||
}
|
||||
assert.expect(2);
|
||||
var collection = new Backbone.Collection([]);
|
||||
assert.strictEqual(collection[$$iterator], collection.values);
|
||||
var iterator = collection[$$iterator]();
|
||||
assert.deepEqual(iterator.next(), {value: void 0, done: true});
|
||||
});
|
||||
|
||||
QUnit.test('Collection.values iterates models in sorted order', function(assert) {
|
||||
assert.expect(4);
|
||||
var one = new Backbone.Model({id: 1});
|
||||
var two = new Backbone.Model({id: 2});
|
||||
var three = new Backbone.Model({id: 3});
|
||||
var collection = new Backbone.Collection([one, two, three]);
|
||||
var iterator = collection.values();
|
||||
assert.strictEqual(iterator.next().value, one);
|
||||
assert.strictEqual(iterator.next().value, two);
|
||||
assert.strictEqual(iterator.next().value, three);
|
||||
assert.strictEqual(iterator.next().value, void 0);
|
||||
});
|
||||
|
||||
QUnit.test('Collection.keys iterates ids in sorted order', function(assert) {
|
||||
assert.expect(4);
|
||||
var one = new Backbone.Model({id: 1});
|
||||
var two = new Backbone.Model({id: 2});
|
||||
var three = new Backbone.Model({id: 3});
|
||||
var collection = new Backbone.Collection([one, two, three]);
|
||||
var iterator = collection.keys();
|
||||
assert.strictEqual(iterator.next().value, 1);
|
||||
assert.strictEqual(iterator.next().value, 2);
|
||||
assert.strictEqual(iterator.next().value, 3);
|
||||
assert.strictEqual(iterator.next().value, void 0);
|
||||
});
|
||||
|
||||
QUnit.test('Collection.entries iterates ids and models in sorted order', function(assert) {
|
||||
assert.expect(4);
|
||||
var one = new Backbone.Model({id: 1});
|
||||
var two = new Backbone.Model({id: 2});
|
||||
var three = new Backbone.Model({id: 3});
|
||||
var collection = new Backbone.Collection([one, two, three]);
|
||||
var iterator = collection.entries();
|
||||
assert.deepEqual(iterator.next().value, [1, one]);
|
||||
assert.deepEqual(iterator.next().value, [2, two]);
|
||||
assert.deepEqual(iterator.next().value, [3, three]);
|
||||
assert.strictEqual(iterator.next().value, void 0);
|
||||
});
|
||||
|
||||
QUnit.test('#3039 #3951: adding at index fires with correct at', function(assert) {
|
||||
assert.expect(4);
|
||||
var collection = new Backbone.Collection([{val: 0}, {val: 4}]);
|
||||
@@ -1995,4 +2095,9 @@
|
||||
assert.equal(fired, false);
|
||||
});
|
||||
|
||||
})();
|
||||
QUnit.test('get models with `attributes` key', function(assert) {
|
||||
var model = {id: 1, attributes: {}};
|
||||
var collection = new Backbone.Collection([model]);
|
||||
assert.ok(collection.get(model));
|
||||
});
|
||||
})(QUnit);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function() {
|
||||
(function(QUnit) {
|
||||
|
||||
QUnit.module('Backbone.Events');
|
||||
|
||||
@@ -703,4 +703,41 @@
|
||||
two.trigger('y', 2);
|
||||
});
|
||||
|
||||
})();
|
||||
QUnit.test('#3611 - listenTo is compatible with non-Backbone event libraries', function(assert) {
|
||||
var obj = _.extend({}, Backbone.Events);
|
||||
var other = {
|
||||
events: {},
|
||||
on: function(name, callback) {
|
||||
this.events[name] = callback;
|
||||
},
|
||||
trigger: function(name) {
|
||||
this.events[name]();
|
||||
}
|
||||
};
|
||||
|
||||
obj.listenTo(other, 'test', function() { assert.ok(true); });
|
||||
other.trigger('test');
|
||||
});
|
||||
|
||||
QUnit.test('#3611 - stopListening is compatible with non-Backbone event libraries', function(assert) {
|
||||
var obj = _.extend({}, Backbone.Events);
|
||||
var other = {
|
||||
events: {},
|
||||
on: function(name, callback) {
|
||||
this.events[name] = callback;
|
||||
},
|
||||
off: function() {
|
||||
this.events = {};
|
||||
},
|
||||
trigger: function(name) {
|
||||
var fn = this.events[name];
|
||||
if (fn) fn();
|
||||
}
|
||||
};
|
||||
|
||||
obj.listenTo(other, 'test', function() { assert.ok(false); });
|
||||
obj.stopListening(other);
|
||||
other.trigger('test');
|
||||
assert.equal(_.size(obj._listeningTo), 0);
|
||||
});
|
||||
})(QUnit);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function() {
|
||||
(function(QUnit) {
|
||||
|
||||
var ProxyModel = Backbone.Model.extend();
|
||||
var Klass = Backbone.Collection.extend({
|
||||
@@ -63,6 +63,36 @@
|
||||
assert.equal(model.get('value'), 2);
|
||||
});
|
||||
|
||||
|
||||
QUnit.test('preinitialize', function(assert) {
|
||||
assert.expect(2);
|
||||
var Model = Backbone.Model.extend({
|
||||
|
||||
preinitialize: function() {
|
||||
this.one = 1;
|
||||
}
|
||||
});
|
||||
var model = new Model({}, {collection: collection});
|
||||
assert.equal(model.one, 1);
|
||||
assert.equal(model.collection, collection);
|
||||
});
|
||||
|
||||
QUnit.test('preinitialize occurs before the model is set up', function(assert) {
|
||||
assert.expect(6);
|
||||
var Model = Backbone.Model.extend({
|
||||
|
||||
preinitialize: function() {
|
||||
assert.equal(this.collection, undefined);
|
||||
assert.equal(this.cid, undefined);
|
||||
assert.equal(this.id, undefined);
|
||||
}
|
||||
});
|
||||
var model = new Model({id: 'foo'}, {collection: collection});
|
||||
assert.equal(model.collection, collection);
|
||||
assert.equal(model.id, 'foo');
|
||||
assert.notEqual(model.cid, undefined);
|
||||
});
|
||||
|
||||
QUnit.test('parse can return null', function(assert) {
|
||||
assert.expect(1);
|
||||
var Model = Backbone.Model.extend({
|
||||
@@ -1365,6 +1395,28 @@
|
||||
assert.ok(!model.set('valid', false, {validate: true}));
|
||||
});
|
||||
|
||||
QUnit.test('mixin', function(assert) {
|
||||
Backbone.Model.mixin({
|
||||
isEqual: function(model1, model2) {
|
||||
return _.isEqual(model1, model2.attributes);
|
||||
}
|
||||
});
|
||||
|
||||
var model1 = new Backbone.Model({
|
||||
a: {b: 2}, c: 3
|
||||
});
|
||||
var model2 = new Backbone.Model({
|
||||
a: {b: 2}, c: 3
|
||||
});
|
||||
var model3 = new Backbone.Model({
|
||||
a: {b: 4}, c: 3
|
||||
});
|
||||
|
||||
assert.equal(model1.isEqual(model2), true);
|
||||
assert.equal(model1.isEqual(model3), false);
|
||||
});
|
||||
|
||||
|
||||
QUnit.test('#1179 - isValid returns true in the absence of validate.', function(assert) {
|
||||
assert.expect(1);
|
||||
var model = new Backbone.Model();
|
||||
@@ -1415,4 +1467,4 @@
|
||||
assert.equal(model.id, 3);
|
||||
});
|
||||
|
||||
})();
|
||||
})(QUnit);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function() {
|
||||
(function(QUnit) {
|
||||
|
||||
QUnit.module('Backbone.noConflict');
|
||||
|
||||
@@ -10,4 +10,4 @@
|
||||
assert.equal(window.Backbone, noconflictBackbone, 'Backbone is still pointing to the original Backbone');
|
||||
});
|
||||
|
||||
})();
|
||||
})(QUnit);
|
||||
|
||||
107
test/router.js
107
test/router.js
@@ -1,4 +1,4 @@
|
||||
(function() {
|
||||
(function(QUnit) {
|
||||
|
||||
var router = null;
|
||||
var location = null;
|
||||
@@ -28,7 +28,8 @@
|
||||
'fragment',
|
||||
'pathname',
|
||||
'protocol'
|
||||
));
|
||||
));
|
||||
|
||||
// In IE, anchor.pathname does not contain a leading slash though
|
||||
// window.location.pathname does.
|
||||
if (!/^\//.test(this.pathname)) this.pathname = '/' + this.pathname;
|
||||
@@ -42,7 +43,7 @@
|
||||
|
||||
QUnit.module('Backbone.Router', {
|
||||
|
||||
setup: function() {
|
||||
beforeEach: function() {
|
||||
location = new Location('http://example.com');
|
||||
Backbone.history = _.extend(new Backbone.History, {location: location});
|
||||
router = new Router({testing: 101});
|
||||
@@ -53,7 +54,7 @@
|
||||
Backbone.history.on('route', onRoute);
|
||||
},
|
||||
|
||||
teardown: function() {
|
||||
afterEach: function() {
|
||||
Backbone.history.stop();
|
||||
Backbone.history.off('route', onRoute);
|
||||
}
|
||||
@@ -95,6 +96,10 @@
|
||||
'*anything': 'anything'
|
||||
},
|
||||
|
||||
preinitialize: function(options) {
|
||||
this.testpreinit = 'foo';
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
this.testing = options.testing;
|
||||
this.route('implicit', 'implicit');
|
||||
@@ -121,19 +126,19 @@
|
||||
this.charType = 'escaped';
|
||||
},
|
||||
|
||||
contacts: function(){
|
||||
contacts: function() {
|
||||
this.contact = 'index';
|
||||
},
|
||||
|
||||
newContact: function(){
|
||||
newContact: function() {
|
||||
this.contact = 'new';
|
||||
},
|
||||
|
||||
loadContact: function(){
|
||||
loadContact: function() {
|
||||
this.contact = 'load';
|
||||
},
|
||||
|
||||
optionalItem: function(arg){
|
||||
optionalItem: function(arg) {
|
||||
this.arg = arg !== void 0 ? arg : null;
|
||||
},
|
||||
|
||||
@@ -181,6 +186,11 @@
|
||||
assert.equal(router.testing, 101);
|
||||
});
|
||||
|
||||
QUnit.test('preinitialize', function(assert) {
|
||||
assert.expect(1);
|
||||
assert.equal(router.testpreinit, 'foo');
|
||||
});
|
||||
|
||||
QUnit.test('routes (simple)', function(assert) {
|
||||
assert.expect(4);
|
||||
location.replace('http://example.com#search/news');
|
||||
@@ -234,10 +244,11 @@
|
||||
assert.ok(Backbone.history.navigate('search/manhattan/p20', true));
|
||||
});
|
||||
|
||||
QUnit.test('route precedence via navigate', function(assert){
|
||||
QUnit.test('route precedence via navigate', function(assert) {
|
||||
assert.expect(6);
|
||||
// check both 0.9.x and backwards-compatibility options
|
||||
_.each([{trigger: true}, true], function( options ){
|
||||
|
||||
// Check both 0.9.x and backwards-compatibility options
|
||||
_.each([{trigger: true}, true], function(options) {
|
||||
Backbone.history.navigate('contacts', options);
|
||||
assert.equal(router.contact, 'index');
|
||||
Backbone.history.navigate('contacts/new', options);
|
||||
@@ -249,7 +260,7 @@
|
||||
|
||||
QUnit.test('loadUrl is not called for identical routes.', function(assert) {
|
||||
assert.expect(0);
|
||||
Backbone.history.loadUrl = function(){ assert.ok(false); };
|
||||
Backbone.history.loadUrl = function() { assert.ok(false); };
|
||||
location.replace('http://example.com#route');
|
||||
Backbone.history.navigate('route');
|
||||
Backbone.history.navigate('/route');
|
||||
@@ -345,9 +356,9 @@
|
||||
assert.strictEqual(router.path, 'c/d/e');
|
||||
});
|
||||
|
||||
QUnit.test("fires event when router doesn't have callback on it", function(assert) {
|
||||
QUnit.test('fires event when router doesn\'t have callback on it', function(assert) {
|
||||
assert.expect(1);
|
||||
router.on('route:noCallback', function(){ assert.ok(true); });
|
||||
router.on('route:noCallback', function() { assert.ok(true); });
|
||||
location.replace('http://example.com#noCallback');
|
||||
Backbone.history.checkUrl();
|
||||
});
|
||||
@@ -536,8 +547,8 @@
|
||||
Backbone.history = _.extend(new Backbone.History, {
|
||||
location: location,
|
||||
history: {
|
||||
pushState: function(){},
|
||||
replaceState: function(){}
|
||||
pushState: function() {},
|
||||
replaceState: function() {}
|
||||
}
|
||||
});
|
||||
Backbone.history.start({root: 'root'});
|
||||
@@ -551,8 +562,8 @@
|
||||
Backbone.history = _.extend(new Backbone.History, {
|
||||
location: location,
|
||||
history: {
|
||||
pushState: function(){},
|
||||
replaceState: function(state, title, url){
|
||||
pushState: function() {},
|
||||
replaceState: function(state, title, url) {
|
||||
assert.strictEqual(url, '/root/x/y');
|
||||
}
|
||||
}
|
||||
@@ -570,8 +581,8 @@
|
||||
Backbone.history = _.extend(new Backbone.History, {
|
||||
location: location,
|
||||
history: {
|
||||
pushState: function(){},
|
||||
replaceState: function(){}
|
||||
pushState: function() {},
|
||||
replaceState: function() {}
|
||||
}
|
||||
});
|
||||
Backbone.history.start({root: ''});
|
||||
@@ -625,8 +636,8 @@
|
||||
Backbone.history = _.extend(new Backbone.History, {
|
||||
location: location,
|
||||
history: {
|
||||
pushState: function(){},
|
||||
replaceState: function(state, title, url){
|
||||
pushState: function() {},
|
||||
replaceState: function(state, title, url) {
|
||||
assert.strictEqual(url, '/root/x/y?a=b');
|
||||
}
|
||||
}
|
||||
@@ -641,8 +652,8 @@
|
||||
assert.expect(1);
|
||||
var MyRouter = Backbone.Router.extend({
|
||||
routes: {'': 'empty'},
|
||||
empty: function(){},
|
||||
route: function(route){
|
||||
empty: function() {},
|
||||
route: function(route) {
|
||||
assert.strictEqual(route, '');
|
||||
}
|
||||
});
|
||||
@@ -655,7 +666,8 @@
|
||||
assert.strictEqual(history.getFragment('fragment '), 'fragment');
|
||||
});
|
||||
|
||||
QUnit.test('#1820 - Leading slash and trailing space.', 1, function(assert) {
|
||||
QUnit.test('#1820 - Leading slash and trailing space.', function(assert) {
|
||||
assert.expect(1);
|
||||
var history = new Backbone.History;
|
||||
assert.strictEqual(history.getFragment('/fragment '), 'fragment');
|
||||
});
|
||||
@@ -670,7 +682,7 @@
|
||||
assert.strictEqual(router.z, '123');
|
||||
});
|
||||
|
||||
QUnit.test("#2062 - Trigger 'route' event on router instance.", function(assert) {
|
||||
QUnit.test('#2062 - Trigger "route" event on router instance.', function(assert) {
|
||||
assert.expect(2);
|
||||
router.on('route', function(name, args) {
|
||||
assert.strictEqual(name, 'routeEvent');
|
||||
@@ -709,8 +721,8 @@
|
||||
Backbone.history = _.extend(new Backbone.History, {
|
||||
location: location,
|
||||
history: {
|
||||
pushState: function(){},
|
||||
replaceState: function(){ assert.ok(false); }
|
||||
pushState: function() {},
|
||||
replaceState: function() { assert.ok(false); }
|
||||
}
|
||||
});
|
||||
Backbone.history.start({
|
||||
@@ -726,8 +738,8 @@
|
||||
Backbone.history = _.extend(new Backbone.History, {
|
||||
location: location,
|
||||
history: {
|
||||
pushState: function(){},
|
||||
replaceState: function(){}
|
||||
pushState: function() {},
|
||||
replaceState: function() {}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -753,7 +765,7 @@
|
||||
Backbone.history = _.extend(new Backbone.History, {
|
||||
location: location,
|
||||
history: {
|
||||
pushState: function(state, title, url){
|
||||
pushState: function(state, title, url) {
|
||||
assert.strictEqual(url, '/root');
|
||||
}
|
||||
}
|
||||
@@ -785,7 +797,7 @@
|
||||
Backbone.history = _.extend(new Backbone.History, {
|
||||
location: location,
|
||||
history: {
|
||||
pushState: function(state, title, url){
|
||||
pushState: function(state, title, url) {
|
||||
assert.strictEqual(url, '/root?x=1');
|
||||
}
|
||||
}
|
||||
@@ -823,7 +835,7 @@
|
||||
assert.expect(1);
|
||||
var MyRouter = Backbone.Router.extend({
|
||||
routes: {
|
||||
path: function(params){
|
||||
path: function(params) {
|
||||
assert.strictEqual(params, 'x=y%3Fz');
|
||||
}
|
||||
}
|
||||
@@ -921,7 +933,7 @@
|
||||
Backbone.history = _.extend(new Backbone.History, {location: location});
|
||||
var MyRouter = Backbone.Router.extend({
|
||||
routes: {'foo/:id/bar': 'foo'},
|
||||
foo: function(){},
|
||||
foo: function() {},
|
||||
execute: function(callback, args, name) {
|
||||
assert.strictEqual(callback, this.foo);
|
||||
assert.deepEqual(args, ['123', 'x=y']);
|
||||
@@ -953,8 +965,8 @@
|
||||
Backbone.history = _.extend(new Backbone.History, {
|
||||
location: location,
|
||||
history: {
|
||||
pushState: function(){ assert.ok(false); },
|
||||
replaceState: function(){ assert.ok(false); }
|
||||
pushState: function() { assert.ok(false); },
|
||||
replaceState: function() { assert.ok(false); }
|
||||
}
|
||||
});
|
||||
Backbone.history.start({pushState: true});
|
||||
@@ -991,14 +1003,14 @@
|
||||
Backbone.history.start({root: '/root', pushState: true});
|
||||
});
|
||||
|
||||
QUnit.test("Paths that don't match the root should not match no root", function(assert) {
|
||||
QUnit.test('Paths that don\'t match the root should not match no root', function(assert) {
|
||||
assert.expect(0);
|
||||
location.replace('http://example.com/foo');
|
||||
Backbone.history.stop();
|
||||
Backbone.history = _.extend(new Backbone.History, {location: location});
|
||||
var MyRouter = Backbone.Router.extend({
|
||||
routes: {
|
||||
foo: function(){
|
||||
foo: function() {
|
||||
assert.ok(false, 'should not match unless root matches');
|
||||
}
|
||||
}
|
||||
@@ -1007,14 +1019,14 @@
|
||||
Backbone.history.start({root: 'root', pushState: true});
|
||||
});
|
||||
|
||||
QUnit.test("Paths that don't match the root should not match roots of the same length", function(assert) {
|
||||
QUnit.test('Paths that don\'t match the root should not match roots of the same length', function(assert) {
|
||||
assert.expect(0);
|
||||
location.replace('http://example.com/xxxx/foo');
|
||||
Backbone.history.stop();
|
||||
Backbone.history = _.extend(new Backbone.History, {location: location});
|
||||
var MyRouter = Backbone.Router.extend({
|
||||
routes: {
|
||||
foo: function(){
|
||||
foo: function() {
|
||||
assert.ok(false, 'should not match unless root matches');
|
||||
}
|
||||
}
|
||||
@@ -1029,7 +1041,7 @@
|
||||
Backbone.history.stop();
|
||||
Backbone.history = _.extend(new Backbone.History, {location: location});
|
||||
var MyRouter = Backbone.Router.extend({
|
||||
routes: {foo: function(){ assert.ok(true); }}
|
||||
routes: {foo: function() { assert.ok(true); }}
|
||||
});
|
||||
var myRouter = new MyRouter;
|
||||
Backbone.history.start({root: 'x+y.z', pushState: true});
|
||||
@@ -1041,7 +1053,7 @@
|
||||
Backbone.history.stop();
|
||||
Backbone.history = _.extend(new Backbone.History, {location: location});
|
||||
var MyRouter = Backbone.Router.extend({
|
||||
routes: {foo: function(){ assert.ok(true); }}
|
||||
routes: {foo: function() { assert.ok(true); }}
|
||||
});
|
||||
var myRouter = new MyRouter;
|
||||
Backbone.history.start({root: '®ooτ', pushState: true});
|
||||
@@ -1053,10 +1065,17 @@
|
||||
Backbone.history.stop();
|
||||
Backbone.history = _.extend(new Backbone.History, {location: location});
|
||||
var MyRouter = Backbone.Router.extend({
|
||||
routes: {'': function(){ assert.ok(true); }}
|
||||
routes: {'': function() { assert.ok(true); }}
|
||||
});
|
||||
var myRouter = new MyRouter;
|
||||
Backbone.history.start({root: '®ooτ', pushState: true});
|
||||
});
|
||||
|
||||
})();
|
||||
QUnit.test('#4025 - navigate updates URL hash as is', function(assert) {
|
||||
assert.expect(1);
|
||||
var route = 'search/has%20space';
|
||||
Backbone.history.navigate(route);
|
||||
assert.strictEqual(location.hash, '#' + route);
|
||||
});
|
||||
|
||||
})(QUnit);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function() {
|
||||
(function(QUnit) {
|
||||
|
||||
var sync = Backbone.sync;
|
||||
var ajax = Backbone.ajax;
|
||||
@@ -14,7 +14,7 @@
|
||||
var env = QUnit.config.current.testEnvironment;
|
||||
|
||||
// We never want to actually call these during tests.
|
||||
history.pushState = history.replaceState = function(){};
|
||||
history.pushState = history.replaceState = function() {};
|
||||
|
||||
// Capture ajax settings for comparison.
|
||||
Backbone.ajax = function(settings) {
|
||||
@@ -42,4 +42,4 @@
|
||||
history.replaceState = replaceState;
|
||||
});
|
||||
|
||||
})();
|
||||
})(QUnit);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function() {
|
||||
(function(QUnit) {
|
||||
|
||||
var Library = Backbone.Collection.extend({
|
||||
url: function() { return '/library'; }
|
||||
@@ -158,7 +158,7 @@
|
||||
|
||||
QUnit.test('Backbone.ajax', function(assert) {
|
||||
assert.expect(1);
|
||||
Backbone.ajax = function(settings){
|
||||
Backbone.ajax = function(settings) {
|
||||
assert.strictEqual(settings.url, '/test');
|
||||
};
|
||||
var model = new Backbone.Model();
|
||||
@@ -236,4 +236,4 @@
|
||||
this.ajaxSettings.error({}, 'textStatus', 'errorThrown');
|
||||
});
|
||||
|
||||
})();
|
||||
})(QUnit);
|
||||
|
||||
47
test/view.js
47
test/view.js
@@ -1,13 +1,13 @@
|
||||
(function() {
|
||||
(function(QUnit) {
|
||||
|
||||
var view;
|
||||
|
||||
QUnit.module('Backbone.View', {
|
||||
|
||||
beforeEach: function(assert) {
|
||||
beforeEach: function() {
|
||||
$('#qunit-fixture').append(
|
||||
'<div id="testElement"><h1>Test</h1></div>'
|
||||
);
|
||||
);
|
||||
|
||||
view = new Backbone.View({
|
||||
id: 'test-view',
|
||||
@@ -61,6 +61,28 @@
|
||||
assert.strictEqual(new View().one, 1);
|
||||
});
|
||||
|
||||
QUnit.test('preinitialize', function(assert) {
|
||||
assert.expect(1);
|
||||
var View = Backbone.View.extend({
|
||||
preinitialize: function() {
|
||||
this.one = 1;
|
||||
}
|
||||
});
|
||||
|
||||
assert.strictEqual(new View().one, 1);
|
||||
});
|
||||
|
||||
QUnit.test('preinitialize occurs before the view is set up', function(assert) {
|
||||
assert.expect(2);
|
||||
var View = Backbone.View.extend({
|
||||
preinitialize: function() {
|
||||
assert.equal(this.el, undefined);
|
||||
}
|
||||
});
|
||||
var _view = new View({});
|
||||
assert.notEqual(_view.el, undefined);
|
||||
});
|
||||
|
||||
QUnit.test('render', function(assert) {
|
||||
assert.expect(1);
|
||||
var myView = new Backbone.View;
|
||||
@@ -72,8 +94,8 @@
|
||||
var counter1 = 0, counter2 = 0;
|
||||
|
||||
var myView = new Backbone.View({el: '#testElement'});
|
||||
myView.increment = function(){ counter1++; };
|
||||
myView.$el.on('click', function(){ counter2++; });
|
||||
myView.increment = function() { counter1++; };
|
||||
myView.$el.on('click', function() { counter2++; });
|
||||
|
||||
var events = {'click h1': 'increment'};
|
||||
|
||||
@@ -129,11 +151,10 @@
|
||||
assert.equal(myView.counter, 3);
|
||||
});
|
||||
|
||||
|
||||
QUnit.test('delegateEvents ignore undefined methods', function(assert) {
|
||||
assert.expect(0);
|
||||
var myView = new Backbone.View({el: '<p></p>'});
|
||||
myView.delegateEvents({'click': 'undefinedMethod'});
|
||||
myView.delegateEvents({click: 'undefinedMethod'});
|
||||
myView.$el.trigger('click');
|
||||
});
|
||||
|
||||
@@ -142,8 +163,8 @@
|
||||
var counter1 = 0, counter2 = 0;
|
||||
|
||||
var myView = new Backbone.View({el: '#testElement'});
|
||||
myView.increment = function(){ counter1++; };
|
||||
myView.$el.on('click', function(){ counter2++; });
|
||||
myView.increment = function() { counter1++; };
|
||||
myView.$el.on('click', function() { counter2++; });
|
||||
|
||||
var events = {'click h1': 'increment'};
|
||||
|
||||
@@ -203,7 +224,7 @@
|
||||
assert.expect(2);
|
||||
var myView = new Backbone.View({el: '#testElement'});
|
||||
myView.delegate('click', function() { assert.ok(true); });
|
||||
var handler = function(){ assert.ok(false); };
|
||||
var handler = function() { assert.ok(false); };
|
||||
myView.delegate('click', 'h1', handler);
|
||||
myView.undelegate('click', 'h1', handler);
|
||||
myView.$('h1').trigger('click');
|
||||
@@ -405,8 +426,8 @@
|
||||
assert.expect(0);
|
||||
var View = Backbone.View.extend({
|
||||
initialize: function() {
|
||||
this.listenTo(this.model, 'all x', function(){ assert.ok(false); });
|
||||
this.listenTo(this.collection, 'all x', function(){ assert.ok(false); });
|
||||
this.listenTo(this.model, 'all x', function() { assert.ok(false); });
|
||||
this.listenTo(this.collection, 'all x', function() { assert.ok(false); });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -492,4 +513,4 @@
|
||||
assert.notEqual($oldEl, myView.$el);
|
||||
});
|
||||
|
||||
})();
|
||||
})(QUnit);
|
||||
|
||||
Reference in New Issue
Block a user