Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

v1.5.2. Improvement.

- v1.5.2 March 21, 2013
	- Backbone peerDependency now supports any version
	- Tested again Backbone.js v1.0.0
	- Re-compiled with CoffeeScript 1.6.2 from 1.4.0
  • Loading branch information...
commit a5ca23d95ad9fe8bc37938128ba7989a028a6fd9 1 parent 26cf4f6
@balupton balupton authored
View
12 .travis.yml
@@ -3,13 +3,13 @@ install: "npm install"
before_script: "./node_modules/.bin/cake test-prepare"
script: "./node_modules/.bin/cake test"
node_js:
- - 0.4
- - 0.6
- - 0.8
- - 0.9
+ - "0.4"
+ - "0.6"
+ - "0.8"
+ - "0.10"
notifications:
irc:
- - "irc.freenode.org#bevry"
+ - "irc.freenode.org#bevry-dev"
email:
recipients:
- - query-engine@bevry.me
+ - travisci@bevry.me
View
5 History.md
@@ -1,5 +1,10 @@
## History
+- v1.5.2 March 21, 2013
+ - Backbone peerDependency now supports any version
+ - Tested again Backbone.js v1.0.0
+ - Re-compiled with CoffeeScript 1.6.2 from 1.4.0
+
- v1.5.1 January 24, 2013
- Support for Backbone v0.9.10
- Closes [pull request #21](https://github.com/bevry/query-engine/pull/21) thanks to [Nicholas Firth-McCoy](https://github.com/nfm)
View
2  README.md
@@ -69,7 +69,7 @@ QueryEngine provides extensive Querying, Filtering, and Searching abilities for
## Compatability
-Tested and working against backbone versions 0.9.2 and 0.9.9.
+Tested and working against Backbone versions 0.9.2, 0.9.9, and 1.0.0
## License
View
7 out/demo/code.js
@@ -15,6 +15,7 @@
$(window).resize(function() {
var padHeight, padWidth;
+
padWidth = $(window).width() / 2 - 20;
padHeight = $(window).height() - $('.header:first').height() - 80;
return $('.pad,.editor').width(padWidth).height(padHeight);
@@ -34,14 +35,16 @@
}
performQuery = function() {
- var code, errMessage, inCollection, resultCoffee, resultCollection;
+ var code, err, errMessage, inCollection, resultCoffee, resultCollection;
+
try {
code = CoffeeScript.compile(editors.code.getSession().getValue());
inCollection = eval(code);
resultCollection = queryEngine.createCollection(inCollection);
resultCoffee = Js2coffee.build('var result = ' + JSON.stringify(resultCollection));
return editors.result.getSession().setValue(resultCoffee);
- } catch (err) {
+ } catch (_error) {
+ err = _error;
errMessage = err.toString();
console.log(err);
return editors.result.getSession().setValue(errMessage);
View
9 out/demo/search.js
@@ -15,6 +15,7 @@
$(window).resize(function() {
var padHeight, padWidth;
+
padWidth = $(window).width() / 2 - 20;
padHeight = $(window).height() - $('.header:first').height() - 80;
return $('.pad,.editor').width(padWidth).height(padHeight);
@@ -22,6 +23,7 @@
$(document).keydown(function(e) {
var isInput;
+
isInput = $(document.activeElement).is(':input');
if (e.keyCode === 8 && !isInput) {
return e.preventDefault();
@@ -42,13 +44,15 @@
}
codeChanged = function() {
- var codeCoffeeScript, codeJavaScript, collection, errMessage;
+ var codeCoffeeScript, codeJavaScript, collection, err, errMessage;
+
try {
codeCoffeeScript = editors.code.getSession().getValue();
codeJavaScript = CoffeeScript.compile(codeCoffeeScript);
collection = eval(codeJavaScript);
return window.updateResults(collection);
- } catch (err) {
+ } catch (_error) {
+ err = _error;
errMessage = err.stack.toString();
console.log(errMessage);
return editors.result.getSession().setValue(errMessage);
@@ -57,6 +61,7 @@
window.updateResults = function(collection) {
var resultArray, resultCoffee, resultJavaScript;
+
resultArray = collection != null ? collection.toJSON() : void 0;
resultJavaScript = JSON.stringify(resultArray);
resultCoffee = Js2coffee.build("var result = " + resultJavaScript);
View
9 out/demo/visual-search.js
@@ -15,6 +15,7 @@
$(window).resize(function() {
var padHeight, padWidth;
+
padWidth = $(window).width() / 2 - 20;
padHeight = $(window).height() - $('.header:first').height() - 80;
return $('.pad,.editor').width(padWidth).height(padHeight);
@@ -22,6 +23,7 @@
$(document).keydown(function(e) {
var isInput;
+
isInput = $(document.activeElement).is(':input');
if (e.keyCode === 8 && !isInput) {
return e.preventDefault();
@@ -42,13 +44,15 @@
}
codeChanged = function() {
- var codeCoffeeScript, codeJavaScript, collection, errMessage;
+ var codeCoffeeScript, codeJavaScript, collection, err, errMessage;
+
try {
codeCoffeeScript = editors.code.getSession().getValue();
codeJavaScript = CoffeeScript.compile(codeCoffeeScript);
collection = eval(codeJavaScript);
return window.updateResults(collection);
- } catch (err) {
+ } catch (_error) {
+ err = _error;
errMessage = err.stack.toString();
console.log(errMessage);
return editors.result.getSession().setValue(errMessage);
@@ -57,6 +61,7 @@
window.updateResults = function(collection) {
var resultArray, resultCoffee, resultJavaScript;
+
resultArray = collection != null ? collection.toJSON() : void 0;
resultJavaScript = JSON.stringify(resultArray);
resultCoffee = Js2coffee.build("var result = " + resultJavaScript);
View
186 out/lib/query-engine.js
@@ -1,5 +1,5 @@
(function() {
- var Backbone, Criteria, Hash, Pill, Query, QueryCollection, queryEngine, util, _ref,
+ var Backbone, Criteria, Hash, Pill, Query, QueryCollection, err, queryEngine, util, _ref, _ref1,
__hasProp = {}.hasOwnProperty,
__slice = [].slice,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
@@ -8,7 +8,8 @@
try {
Backbone = (_ref = (typeof module !== "undefined" && module !== null ? require('backbone') : this.Backbone)) != null ? _ref : null;
- } catch (err) {
+ } catch (_error) {
+ err = _error;
Backbone = null;
}
@@ -67,6 +68,7 @@
},
isObjectEmpty: function(object) {
var empty, key, value;
+
empty = true;
for (key in object) {
if (!__hasProp.call(object, key)) continue;
@@ -84,16 +86,19 @@
},
clone: function() {
var args;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return util.shallowExtendPlainObjects.apply(util, [{}].concat(__slice.call(args)));
},
extend: function() {
var args;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return util.shallowExtendPlainObjects.apply(util, args);
},
shallowExtendPlainObjects: function() {
var key, obj, objs, target, value, _i, _len;
+
target = arguments[0], objs = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
for (_i = 0, _len = objs.length; _i < _len; _i++) {
obj = objs[_i];
@@ -108,6 +113,7 @@
},
get: function(obj, key) {
var result;
+
if (obj.get != null) {
result = obj.get(key);
} else {
@@ -134,6 +140,7 @@
},
toArray: function(value) {
var item, key, result, valueExists;
+
result = [];
valueExists = typeof value !== 'undefined';
if (valueExists) {
@@ -153,6 +160,7 @@
},
toArrayGroup: function(value) {
var item, key, obj, result, valueExists;
+
result = [];
valueExists = typeof value !== 'undefined';
if (valueExists) {
@@ -174,6 +182,7 @@
},
generateComparator: function(input) {
var generateFunction;
+
generateFunction = function(comparator) {
if (!comparator) {
throw new Error('Cannot sort without a comparator');
@@ -182,6 +191,7 @@
} else if (util.isArray(comparator)) {
return function(a, b) {
var comparison, key, value, _i, _len;
+
comparison = 0;
for (key = _i = 0, _len = comparator.length; _i < _len; key = ++_i) {
value = comparator[key];
@@ -195,6 +205,7 @@
} else if (util.isObject(comparator)) {
return function(a, b) {
var aValue, bValue, comparison, key, value;
+
comparison = 0;
for (key in comparator) {
if (!__hasProp.call(comparator, key)) continue;
@@ -226,11 +237,11 @@
};
Hash = (function(_super) {
-
__extends(Hash, _super);
function Hash(value) {
var item, key, _i, _len;
+
value = util.toArray(value);
for (key = _i = 0, _len = value.length; _i < _len; key = ++_i) {
item = value[key];
@@ -240,6 +251,7 @@
Hash.prototype.hasIn = function(options) {
var value, _i, _len;
+
options = util.toArray(options);
for (_i = 0, _len = this.length; _i < _len; _i++) {
value = this[_i];
@@ -252,6 +264,7 @@
Hash.prototype.hasAll = function(options) {
var empty, pass, value, _i, _len;
+
options = util.toArray(options);
empty = true;
pass = true;
@@ -270,6 +283,7 @@
Hash.prototype.isSame = function(options) {
var pass;
+
options = util.toArray(options);
pass = this.sort().join() === options.sort().join();
return pass;
@@ -283,35 +297,31 @@
QueryCollection = null;
} else {
QueryCollection = (function(_super) {
-
__extends(QueryCollection, _super);
function QueryCollection() {
this.onParentReset = __bind(this.onParentReset, this);
-
this.onParentAdd = __bind(this.onParentAdd, this);
-
this.onParentRemove = __bind(this.onParentRemove, this);
-
this.onParentChange = __bind(this.onParentChange, this);
-
- this.onChange = __bind(this.onChange, this);
- return QueryCollection.__super__.constructor.apply(this, arguments);
+ this.onChange = __bind(this.onChange, this); _ref1 = QueryCollection.__super__.constructor.apply(this, arguments);
+ return _ref1;
}
QueryCollection.prototype.model = Backbone.Model;
QueryCollection.prototype.initialize = function(models, options) {
- var key, me, value, _ref1, _ref2, _ref3;
+ var key, me, value, _ref2, _ref3, _ref4;
+
me = this;
- if ((_ref1 = this.options) == null) {
+ if ((_ref2 = this.options) == null) {
this.options = {};
}
- _ref2 = Criteria.prototype;
- for (key in _ref2) {
- if (!__hasProp.call(_ref2, key)) continue;
- value = _ref2[key];
- if ((_ref3 = this[key]) == null) {
+ _ref3 = Criteria.prototype;
+ for (key in _ref3) {
+ if (!__hasProp.call(_ref3, key)) continue;
+ value = _ref3[key];
+ if ((_ref4 = this[key]) == null) {
this[key] = value;
}
}
@@ -342,13 +352,14 @@
};
QueryCollection.prototype.createChildCollection = function(models, options) {
- var collection, _ref1, _ref2;
+ var collection, _ref2, _ref3;
+
options || (options = {});
options.parentCollection = this;
- if ((_ref1 = options.collection) == null) {
+ if ((_ref2 = options.collection) == null) {
options.collection = this.collection || QueryCollection;
}
- if ((_ref2 = options.comparator) == null) {
+ if ((_ref3 = options.comparator) == null) {
options.comparator = options.collection.prototype.comparator || this.comparator;
}
collection = new options.collection(models, options);
@@ -357,6 +368,7 @@
QueryCollection.prototype.createLiveChildCollection = function(models, options) {
var collection;
+
options || (options = {});
options.live = true;
collection = this.createChildCollection(models, options);
@@ -381,11 +393,12 @@
};
QueryCollection.prototype.hasModel = function(model) {
- var exists, _ref1, _ref2;
+ var exists, _ref2, _ref3;
+
model || (model = {});
if ((model.id != null) && this.get(model.id)) {
exists = true;
- } else if ((model.cid != null) && ((_ref1 = (_ref2 = this._byCid) != null ? _ref2[model.cid] : void 0) != null ? _ref1 : this.get(model.cid))) {
+ } else if ((model.cid != null) && ((_ref2 = (_ref3 = this._byCid) != null ? _ref3[model.cid] : void 0) != null ? _ref2 : this.get(model.cid))) {
exists = true;
} else {
exists = false;
@@ -395,6 +408,7 @@
QueryCollection.prototype.safeRemove = function(model) {
var exists;
+
exists = this.hasModel(model);
if (exists) {
this.remove(model);
@@ -404,6 +418,7 @@
QueryCollection.prototype.safeAdd = function(model) {
var exists;
+
exists = this.hasModel(model);
if (!exists) {
this.add(model);
@@ -428,6 +443,7 @@
QueryCollection.prototype.sortArray = function(comparator) {
var arr;
+
arr = this.toJSON();
if (comparator) {
comparator = util.generateComparator(comparator);
@@ -445,6 +461,7 @@
QueryCollection.prototype.findAll = function() {
var args, collection, comparator, criteriaOptions, paging, query;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
if (args.length) {
if (args.length === 1 && args[0] instanceof Criteria) {
@@ -468,6 +485,7 @@
QueryCollection.prototype.findAllLive = function() {
var args, collection, comparator, criteriaOptions, paging, query;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
if (args.length) {
if (args.length === 1 && args[0] instanceof Criteria) {
@@ -491,6 +509,7 @@
QueryCollection.prototype.findOne = function() {
var args, comparator, criteriaOptions, paging, passed, query;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
if (args.length) {
if (args.length === 1 && args[0] instanceof Criteria) {
@@ -518,6 +537,7 @@
QueryCollection.prototype.query = function() {
var args, criteria, passed;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
if (args.length === 1) {
if (args[0] instanceof Criteria) {
@@ -535,6 +555,7 @@
QueryCollection.prototype.queryModels = function() {
var args, collection, criteriaOptions, models, passed;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
criteriaOptions = this.extractCriteriaOptions.apply(this, args);
collection = this.getParentCollection() || this;
@@ -545,6 +566,7 @@
QueryCollection.prototype.queryArray = function() {
var args, model, passed, result, _i, _len;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
result = [];
passed = this.queryModels.apply(this, args);
@@ -557,6 +579,7 @@
QueryCollection.prototype.live = function(enabled) {
var parentCollection;
+
if (enabled == null) {
enabled = this.options.live;
}
@@ -585,6 +608,7 @@
QueryCollection.prototype.add = function(models, options) {
var model, passedModels, _i, _len;
+
options = options ? util.clone(options) : {};
models = util.isArray(models) ? models.slice() : [models];
passedModels = [];
@@ -610,6 +634,7 @@
QueryCollection.prototype.onChange = function(model) {
var pass;
+
pass = this.test(model);
if (!pass) {
this.safeRemove(model);
@@ -623,6 +648,7 @@
QueryCollection.prototype.onParentChange = function(model) {
var pass;
+
pass = this.test(model) && this.getParentCollection().hasModel(model);
if (pass) {
this.safeAdd(model);
@@ -653,19 +679,18 @@
}
Criteria = (function() {
-
function Criteria() {
var args;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
this.applyCriteriaOptions = __bind(this.applyCriteriaOptions, this);
-
this.applyCriteriaOptions.apply(this, args);
this;
-
}
Criteria.prototype.extractCriteriaOptions = function() {
var args, comparator, criteriaOptions, paging, query;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
if (args.length === 1) {
if (args[0] instanceof Criteria) {
@@ -691,27 +716,28 @@
};
Criteria.prototype.applyCriteriaOptions = function() {
- var args, criteriaOptions, _base, _base1, _base2, _base3, _base4, _base5, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7;
+ var args, criteriaOptions, _base, _base1, _base2, _base3, _base4, _base5, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
- if ((_ref1 = this.options) == null) {
+ if ((_ref2 = this.options) == null) {
this.options = {};
}
- if ((_ref2 = (_base = this.options).filters) == null) {
+ if ((_ref3 = (_base = this.options).filters) == null) {
_base.filters = {};
}
- if ((_ref3 = (_base1 = this.options).queries) == null) {
+ if ((_ref4 = (_base1 = this.options).queries) == null) {
_base1.queries = {};
}
- if ((_ref4 = (_base2 = this.options).pills) == null) {
+ if ((_ref5 = (_base2 = this.options).pills) == null) {
_base2.pills = {};
}
- if ((_ref5 = (_base3 = this.options).paging) == null) {
+ if ((_ref6 = (_base3 = this.options).paging) == null) {
_base3.paging = {};
}
- if ((_ref6 = (_base4 = this.options).searchString) == null) {
+ if ((_ref7 = (_base4 = this.options).searchString) == null) {
_base4.searchString = null;
}
- if ((_ref7 = (_base5 = this.options).comparator) == null) {
+ if ((_ref8 = (_base5 = this.options).comparator) == null) {
_base5.comparator = null;
}
criteriaOptions = this.extractCriteriaOptions.apply(this, args);
@@ -771,6 +797,7 @@
Criteria.prototype.setFilters = function(filters) {
var key, value;
+
filters || (filters = {});
for (key in filters) {
if (!__hasProp.call(filters, key)) continue;
@@ -782,6 +809,7 @@
Criteria.prototype.setFilter = function(name, value) {
var filters;
+
if (typeof value === 'undefined') {
throw new Error('QueryCollection::setFilter was called without both arguments');
}
@@ -804,6 +832,7 @@
Criteria.prototype.setQueries = function(queries) {
var key, value;
+
queries || (queries = {});
for (key in queries) {
if (!__hasProp.call(queries, key)) continue;
@@ -815,6 +844,7 @@
Criteria.prototype.setQuery = function(name, value) {
var queries;
+
if (typeof value === 'undefined') {
throw new Error('QueryCollection::setQuery was called without both arguments');
}
@@ -840,6 +870,7 @@
Criteria.prototype.setPills = function(pills) {
var key, value;
+
pills || (pills = {});
for (key in pills) {
if (!__hasProp.call(pills, key)) continue;
@@ -851,6 +882,7 @@
Criteria.prototype.setPill = function(name, value) {
var pills, searchString;
+
if (typeof value === 'undefined') {
throw new Error('QueryCollection::setPill was called without both arguments');
}
@@ -880,6 +912,7 @@
Criteria.prototype.setSearchString = function(searchString) {
var cleanedSearchString, pill, pillName, pills;
+
pills = this.options.pills;
cleanedSearchString = searchString;
for (pillName in pills) {
@@ -894,21 +927,24 @@
Criteria.prototype.test = function() {
var args;
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return this.testModel.apply(this, args);
};
Criteria.prototype.testModel = function(model, criteriaOptions) {
var passed;
+
passed = this.testQueries(model, criteriaOptions != null ? criteriaOptions.queries : void 0) && this.testFilters(model, criteriaOptions != null ? criteriaOptions.filters : void 0) && this.testPills(model, criteriaOptions != null ? criteriaOptions.pills : void 0);
return passed;
};
Criteria.prototype.testModels = function(models, criteriaOptions) {
- var comparator, finish, me, model, paging, pass, passed, start, _i, _len, _ref1;
+ var comparator, finish, me, model, paging, pass, passed, start, _i, _len, _ref2;
+
me = this;
passed = [];
- paging = (_ref1 = criteriaOptions != null ? criteriaOptions.paging : void 0) != null ? _ref1 : this.getPaging();
+ paging = (_ref2 = criteriaOptions != null ? criteriaOptions.paging : void 0) != null ? _ref2 : this.getPaging();
comparator = (criteriaOptions != null ? criteriaOptions.comparator : void 0) != null ? util.generateComparator(criteriaOptions != null ? criteriaOptions.comparator : void 0) : this.getComparator();
for (_i = 0, _len = models.length; _i < _len; _i++) {
model = models[_i];
@@ -935,6 +971,7 @@
Criteria.prototype.testQueries = function(model, queries) {
var passed, query, queryName;
+
passed = true;
if (queries == null) {
queries = this.getQueries();
@@ -956,6 +993,7 @@
Criteria.prototype.testFilters = function(model, filters) {
var cleanedSearchString, filter, filterName, passed;
+
passed = true;
cleanedSearchString = this.getCleanedSearchString();
if (filters == null) {
@@ -974,6 +1012,7 @@
Criteria.prototype.testPills = function(model, pills) {
var passed, pill, pillName, searchString;
+
passed = true;
searchString = this.getSearchString();
if (pills == null) {
@@ -1002,7 +1041,6 @@
})();
Pill = (function() {
-
Pill.prototype.callback = null;
Pill.prototype.regex = null;
@@ -1016,7 +1054,8 @@
Pill.prototype.logicalOperator = 'OR';
function Pill(pill) {
- var prefix, regexString, safePrefixes, safePrefixesStr, _i, _len, _ref1;
+ var prefix, regexString, safePrefixes, safePrefixesStr, _i, _len, _ref2;
+
pill || (pill = {});
this.callback = pill.callback;
this.prefixes = pill.prefixes;
@@ -1024,20 +1063,20 @@
this.logicalOperator = pill.logicalOperator;
}
safePrefixes = [];
- _ref1 = this.prefixes;
- for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
- prefix = _ref1[_i];
+ _ref2 = this.prefixes;
+ for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
+ prefix = _ref2[_i];
safePrefixes.push(util.safeRegex(prefix));
}
safePrefixesStr = safePrefixes.join('|');
regexString = "(" + safePrefixesStr + ")\\s*('[^']+'|\\\"[^\\\"]+\\\"|[^'\\\"\\s]\\S*)";
this.regex = util.createRegex(regexString);
this;
-
}
Pill.prototype.setSearchString = function(searchString) {
var cleanedSearchString, match, value, values;
+
cleanedSearchString = searchString;
values = [];
while (match = this.regex.exec(searchString)) {
@@ -1064,13 +1103,14 @@
};
Pill.prototype.test = function(model) {
- var pass, value, _i, _j, _len, _len1, _ref1, _ref2, _ref3;
- if ((_ref1 = this.values) != null ? _ref1.length : void 0) {
+ var pass, value, _i, _j, _len, _len1, _ref2, _ref3, _ref4;
+
+ if ((_ref2 = this.values) != null ? _ref2.length : void 0) {
if (this.logicalOperator === 'OR') {
pass = false;
- _ref2 = this.values;
- for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
- value = _ref2[_i];
+ _ref3 = this.values;
+ for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
+ value = _ref3[_i];
pass = this.callback(model, value);
if (pass) {
break;
@@ -1078,9 +1118,9 @@
}
} else if (this.logicalOperator === 'AND') {
pass = false;
- _ref3 = this.values;
- for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) {
- value = _ref3[_j];
+ _ref4 = this.values;
+ for (_j = 0, _len1 = _ref4.length; _j < _len1; _j++) {
+ value = _ref4[_j];
pass = this.callback(model, value);
if (!pass) {
break;
@@ -1100,7 +1140,6 @@
})();
Query = (function() {
-
Query.prototype.source = null;
Query.prototype.compiledSelectors = null;
@@ -1109,6 +1148,7 @@
'$or': {
compile: function(opts) {
var queries, query, queryGroup, querySource, _i, _len;
+
queries = [];
queryGroup = util.toArrayGroup(opts.selectorValue);
if (!queryGroup.length) {
@@ -1124,10 +1164,11 @@
};
},
test: function(opts) {
- var query, _i, _len, _ref1;
- _ref1 = opts.queries;
- for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
- query = _ref1[_i];
+ var query, _i, _len, _ref2;
+
+ _ref2 = opts.queries;
+ for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
+ query = _ref2[_i];
if (query.test(opts.model)) {
return true;
}
@@ -1148,10 +1189,11 @@
return opts.selector('$or', opts);
},
test: function(opts) {
- var query, _i, _len, _ref1;
- _ref1 = opts.queries;
- for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
- query = _ref1[_i];
+ var query, _i, _len, _ref2;
+
+ _ref2 = opts.queries;
+ for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
+ query = _ref2[_i];
if (query.test(opts.model) === false) {
return false;
}
@@ -1205,6 +1247,7 @@
'$beginsWith': {
test: function(opts) {
var beginsWithParts, beginsWithValue, _i, _len;
+
if (opts.selectorValue && opts.modelValueExists && util.isString(opts.modelValue)) {
beginsWithParts = util.toArray(opts.selectorValue);
for (_i = 0, _len = beginsWithParts.length; _i < _len; _i++) {
@@ -1226,6 +1269,7 @@
'$endsWith': {
test: function(opts) {
var endsWithParts, endsWithValue, _i, _len;
+
if (opts.selectorValue && opts.modelValueExists && util.isString(opts.modelValue)) {
endsWithParts = util.toArray(opts.selectorValue);
for (_i = 0, _len = endsWithParts.length; _i < _len; _i++) {
@@ -1344,6 +1388,7 @@
'$mod': {
test: function(opts) {
var $mod;
+
if (opts.modelValueExists) {
$mod = opts.selectorValue;
if (!util.isArray($mod)) {
@@ -1440,6 +1485,7 @@
Query.prototype.compileSelector = function(selectorName, selectorOpts) {
var compileOpts, compiledSelector, key, opts, query, selector, selectors, value;
+
if (selectorOpts == null) {
selectorOpts = {};
}
@@ -1480,6 +1526,7 @@
Query.prototype.testCompiledSelector = function(compiledSelector, model) {
var match, opts, test;
+
opts = compiledSelector.opts;
test = compiledSelector.test;
opts.model = model;
@@ -1494,13 +1541,14 @@
};
Query.prototype.compileQuery = function() {
- var advancedSelectorName, advancedSelectorValue, compiledSelector, compiledSelectors, fieldName, query, selectorValue, _ref1;
+ var advancedSelectorName, advancedSelectorValue, compiledSelector, compiledSelectors, fieldName, query, selectorValue, _ref2;
+
query = this;
compiledSelectors = [];
- _ref1 = this.source;
- for (fieldName in _ref1) {
- if (!__hasProp.call(_ref1, fieldName)) continue;
- selectorValue = _ref1[fieldName];
+ _ref2 = this.source;
+ for (fieldName in _ref2) {
+ if (!__hasProp.call(_ref2, fieldName)) continue;
+ selectorValue = _ref2[fieldName];
if (fieldName === '$or' || fieldName === '$nor' || fieldName === '$and' || fieldName === '$not') {
compiledSelector = this.compileSelector(fieldName, {
fieldName: fieldName,
@@ -1566,11 +1614,12 @@
};
Query.prototype.test = function(model) {
- var compiledSelector, match, _i, _len, _ref1;
+ var compiledSelector, match, _i, _len, _ref2;
+
match = true;
- _ref1 = this.compiledSelectors;
- for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
- compiledSelector = _ref1[_i];
+ _ref2 = this.compiledSelectors;
+ for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
+ compiledSelector = _ref2[_i];
match = this.testCompiledSelector(compiledSelector, model);
if (match === false) {
break;
@@ -1606,6 +1655,7 @@
},
testModels: function() {
var args, criteria, models, result;
+
models = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
models = util.toArray(models);
criteria = (function(func, args, ctor) {
@@ -1618,12 +1668,14 @@
},
createCollection: function(models, options) {
var collection;
+
models = util.toArray(models);
collection = new QueryCollection(models, options);
return collection;
},
createLiveCollection: function(models, options) {
var collection;
+
models = util.toArray(models);
collection = new QueryCollection(models, options).live(true);
return collection;
View
1  out/test/everything-test.js
@@ -1,5 +1,4 @@
(function() {
-
require(__dirname + '/live-test');
require(__dirname + '/misc-test');
View
38 out/test/live-test.js
@@ -96,6 +96,7 @@
describe('queries', function(describe, it) {
it('should only keep jquery related models', function() {
var actual, expected, liveCollection;
+
liveCollection = queryEngine.createLiveCollection().setQuery('only jquery related', {
tags: {
$has: ['jquery']
@@ -107,8 +108,10 @@
});
it('should support searching', function() {
var actual, expected, liveCollection;
+
liveCollection = queryEngine.createLiveCollection().setFilter('search', function(model, searchString) {
var pass, searchRegex;
+
searchRegex = queryEngine.createSafeRegex(searchString);
pass = searchRegex.test(model.get('title')) || searchRegex.test(model.get('content'));
return pass;
@@ -120,10 +123,12 @@
return describe('pill searches', function(describe, it) {
it('should support pill searches without spacing', function() {
var actual, expected, liveCollection;
+
liveCollection = queryEngine.createLiveCollection().setPill('id', {
prefixes: ['id:', '#'],
callback: function(model, value) {
var pass, pillRegex;
+
pillRegex = queryEngine.createSafeRegex(value);
pass = pillRegex.test(model.get('id'));
return pass;
@@ -135,10 +140,12 @@
});
it('should support pill searches with null', function() {
var actual, expected, liveCollection;
+
liveCollection = queryEngine.createLiveCollection().setPill('positionNullable', {
prefixes: ['positionNullable:'],
callback: function(model, value) {
var pass, pillRegex;
+
pillRegex = queryEngine.createSafeRegex(value);
pass = pillRegex.test(model.get('positionNullable'));
return pass;
@@ -150,10 +157,12 @@
});
it('should support pill searches with spacing', function() {
var actual, expected, liveCollection;
+
liveCollection = queryEngine.createLiveCollection().setPill('id', {
prefixes: ['id:', '#'],
callback: function(model, value) {
var pass, pillRegex;
+
pillRegex = queryEngine.createSafeRegex(value);
pass = pillRegex.test(model.get('id'));
return pass;
@@ -165,10 +174,12 @@
});
it('should support pill searches with quotes', function() {
var actual, expected, liveCollection;
+
liveCollection = queryEngine.createLiveCollection().setPill('title', {
prefixes: ['title:'],
callback: function(model, value) {
var pass;
+
pass = value === model.get('title');
return pass;
}
@@ -179,10 +190,12 @@
});
it('should support pill searches with OR pills', function() {
var actual, expected, liveCollection;
+
liveCollection = queryEngine.createLiveCollection().setPill('tag', {
prefixes: ['tag:'],
callback: function(model, value) {
var pass;
+
pass = __indexOf.call(model.get('tags'), value) >= 0;
return pass;
}
@@ -193,11 +206,13 @@
});
it('should support pill searches with AND pills', function() {
var actual, expected, liveCollection;
+
liveCollection = queryEngine.createLiveCollection().setPill('tag', {
logicalOperator: 'AND',
prefixes: ['tag:'],
callback: function(model, value) {
var pass;
+
pass = __indexOf.call(model.get('tags'), value) >= 0;
return pass;
}
@@ -208,8 +223,10 @@
});
return it('should support pills searches with filters', function() {
var actual, expected, liveCollection;
+
liveCollection = queryEngine.createLiveCollection().setFilter('search', function(model, searchString) {
var pass, searchRegex;
+
searchRegex = queryEngine.createSafeRegex(searchString);
pass = searchRegex.test(model.get('content'));
return pass;
@@ -217,6 +234,7 @@
prefixes: ['category:'],
callback: function(model, value) {
var pass, pillRegex;
+
pillRegex = queryEngine.createSafeRegex(value);
pass = pillRegex.test(model.get('category'));
return pass;
@@ -230,9 +248,11 @@
});
describe('events', function(describe, it) {
var liveCollection;
+
liveCollection = queryEngine.createLiveCollection();
it('when query is called on our liveCollection, it should successfully filter our models', function() {
var actual, expected;
+
liveCollection.add(models).setQuery('only jquery related', {
tags: {
$has: ['jquery']
@@ -244,6 +264,7 @@
});
it('when a model that passes our rules is added to our liveCollection, it should be added', function() {
var actual, expected;
+
liveCollection.add(ajaxyModel);
actual = liveCollection.toJSON();
expected = [modelsObject.jquery, modelsObject.history, ajaxyModel];
@@ -251,6 +272,7 @@
});
it('when a model that fails our rules is added to our liveCollection, it should NOT be added', function() {
var actual, expected;
+
liveCollection.add(pokemonModel);
actual = liveCollection.toJSON();
expected = [modelsObject.jquery, modelsObject.history, ajaxyModel];
@@ -258,6 +280,7 @@
});
it('when a model is removed from our liveCollection, it should be removed', function() {
var actual, expected;
+
liveCollection.remove(liveCollection.get('history'));
actual = liveCollection.toJSON();
expected = [modelsObject.jquery, ajaxyModel];
@@ -265,6 +288,7 @@
});
it('when a model is changed in our liveCollection (and no longer supports our rules), it should be removed', function() {
var actual, expected;
+
liveCollection.get('jquery').set('tags', []);
actual = liveCollection.toJSON();
expected = [ajaxyModel];
@@ -272,6 +296,7 @@
});
return it('when our liveCollection is reset, it should be empty', function() {
var actual, expected;
+
liveCollection.reset([]);
actual = liveCollection.toJSON();
expected = [];
@@ -280,9 +305,11 @@
});
describe('parent collections', function(describe, it) {
var childCollection, parentCollection;
+
parentCollection = queryEngine.createCollection(models);
it('should work with findAllLive with query', function() {
var actual, childCollection, expected;
+
childCollection = parentCollection.findAllLive({
tags: {
$has: ['jquery']
@@ -294,6 +321,7 @@
});
it('should work with findAllLive with query and comparator', function() {
var actual, childCollection, expected;
+
childCollection = parentCollection.findAllLive({
tags: {
$has: ['jquery']
@@ -308,6 +336,7 @@
childCollection = parentCollection.createLiveChildCollection();
it('when query is called on our childCollection, it should successfully filter our parentCollection', function() {
var actual, expected;
+
childCollection.setQuery('only jquery related', {
tags: {
$has: ['jquery']
@@ -319,6 +348,7 @@
});
it('when a model that passes our rules is added to the parentCollection, it should be added to the childCollection', function() {
var actual, expected;
+
parentCollection.add(ajaxyModel);
actual = childCollection.toJSON();
expected = [modelsObject.jquery, modelsObject.history, ajaxyModel];
@@ -326,6 +356,7 @@
});
it('when a model that fails our rules is added to the parentCollection, it should NOT be added to the childCollection', function() {
var actual, expected;
+
parentCollection.add(pokemonModel);
actual = childCollection.toJSON();
expected = [modelsObject.jquery, modelsObject.history, ajaxyModel];
@@ -333,6 +364,7 @@
});
it('when a model is removed from our parentCollection, it should be removed from our childCollection', function() {
var actual, expected;
+
parentCollection.remove(parentCollection.get('history'));
actual = childCollection.toJSON();
expected = [modelsObject.jquery, ajaxyModel];
@@ -340,6 +372,7 @@
});
it('when a model is changed from our parentCollection (and no longer supports our rules), it should be removed from our childCollection', function() {
var actual, expected;
+
parentCollection.get('jquery').set('tags', []);
actual = childCollection.toJSON();
expected = [ajaxyModel];
@@ -347,6 +380,7 @@
});
it('when a model is changed from our parentCollection (and now supports our rules), it should be added to our childCollection', function() {
var actual, expected;
+
parentCollection.get('jquery').set('tags', ['jquery']);
actual = childCollection.toJSON();
expected = [ajaxyModel, modelsObject.jquery];
@@ -354,6 +388,7 @@
});
return it('when our parentCollection is reset, our childCollection should be reset too', function() {
var actual, expected;
+
parentCollection.reset([]);
actual = childCollection.toJSON();
expected = [];
@@ -362,6 +397,7 @@
});
return describe('parent collections: many levels', function(describe, it) {
var childCollectionLevel2, childCollectionLevel3, childCollectionLevel4, parentCollection;
+
parentCollection = queryEngine.createCollection(models);
childCollectionLevel2 = parentCollection.findAllLive({
tags: {
@@ -378,6 +414,7 @@
});
it('removes triggered by changes trickle through children correctly', function() {
var actual, expected;
+
parentCollection.where({
id: 'history'
})[0].set({
@@ -389,6 +426,7 @@
});
return it('additions triggered by changes trickle through children correctly', function() {
var actual, expected;
+
parentCollection.where({
id: 'history'
})[0].set({
View
7 out/test/misc-test.js
@@ -16,13 +16,14 @@
describe('misc', function(describe, it) {
return describe('collection property', function(describe, it) {
return it('when specificied, should create child collections of the property type', function() {
- var MyQueryCollection, myChildCollection, myQueryCollection;
- MyQueryCollection = (function(_super) {
+ var MyQueryCollection, myChildCollection, myQueryCollection, _ref;
+ MyQueryCollection = (function(_super) {
__extends(MyQueryCollection, _super);
function MyQueryCollection() {
- return MyQueryCollection.__super__.constructor.apply(this, arguments);
+ _ref = MyQueryCollection.__super__.constructor.apply(this, arguments);
+ return _ref;
}
MyQueryCollection.prototype.collection = MyQueryCollection;
View
11 out/test/queries-test.js
@@ -453,6 +453,7 @@
return _.each(queryTests, function(queryTest, queryTestName) {
return it(queryTestName, function() {
var actual, criteriaOptions, expected, expectedModelId, _i, _len, _ref;
+
if (queryTest.debug) {
debugger;
}
@@ -483,6 +484,7 @@
return _.each(queryTests, function(queryTest, queryTestName) {
return it(queryTestName, function() {
var actual, expected, expectedModelId, expectedModels, _i, _len, _ref;
+
if (queryTest.debug) {
debugger;
}
@@ -507,12 +509,14 @@
describe('special', function(describe, it) {
it('all', function() {
var actual, expected;
+
actual = store;
expected = store;
return assert.deepEqual(actual.toJSON(), expected.toJSON());
});
return it('findOne', function() {
var actual, expected;
+
actual = store.findOne({
tags: {
$has: 'jquery'
@@ -524,6 +528,7 @@
return describe('paging', function(describe, it) {
it('limit', function() {
var actual, expected;
+
actual = store.createChildCollection().query({
limit: 1
});
@@ -534,6 +539,7 @@
});
it('limit+page', function() {
var actual, expected;
+
actual = store.createChildCollection().query({
limit: 1,
page: 2
@@ -545,6 +551,7 @@
});
it('limit+offset', function() {
var actual, expected;
+
actual = store.createChildCollection().query({
limit: 1,
offset: 1
@@ -556,6 +563,7 @@
});
it('limit+offset+page', function() {
var actual, expected;
+
actual = store.createChildCollection().query({
limit: 1,
offset: 1,
@@ -569,6 +577,7 @@
it('limit+offset+page (via findAll)', function() {
debugger;
var actual, expected;
+
actual = store.findAll({
id: {
$exists: true
@@ -585,6 +594,7 @@
});
return it('offset', function() {
var actual, expected;
+
actual = store.createChildCollection().query({
offset: 1
});
@@ -601,6 +611,7 @@
describe('queries', function(describe, it) {
var store, storeName, _results;
+
_results = [];
for (storeName in stores) {
if (!__hasProp.call(stores, storeName)) continue;
View
16 out/test/sort-test.js
@@ -104,6 +104,7 @@
describe('sortArray', function(describe, it) {
it('string-object', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).sortArray({
title: 1
});
@@ -112,6 +113,7 @@
});
it('numeric-function', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).sortArray(function(a, b) {
return b.position - a.position;
});
@@ -120,6 +122,7 @@
});
it('numeric-object', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).sortArray({
position: -1
});
@@ -128,6 +131,7 @@
});
it('date-function', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).sortArray(function(a, b) {
return b.date - a.date;
});
@@ -136,6 +140,7 @@
});
return it('date-object', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).sortArray({
date: -1
});
@@ -146,6 +151,7 @@
describe('sortCollection', function(describe, it) {
it('numeric-function', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).sortCollection(function(a, b) {
return b.get('position') - a.get('position');
});
@@ -154,6 +160,7 @@
});
it('numeric-object', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).sortCollection({
position: -1
});
@@ -162,6 +169,7 @@
});
it('date-function', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).sortCollection(function(a, b) {
return b.get('date') - a.get('date');
});
@@ -170,6 +178,7 @@
});
return it('date-object', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).sortCollection({
date: -1
});
@@ -180,6 +189,7 @@
describe('queryArray', function(describe, it) {
it('queryArray', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).queryArray({
tags: {
$has: 'jquery'
@@ -192,6 +202,7 @@
});
return it('queryArray-paging', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).queryArray({
tags: {
$has: 'jquery'
@@ -208,6 +219,7 @@
describe('findAll', function(describe, it) {
it('findAll', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).findAll({
tags: {
$has: 'jquery'
@@ -220,6 +232,7 @@
});
return it('findAll-paging', function() {
var actual, expected;
+
actual = queryEngine.createCollection(docs.models).findAll({
tags: {
$has: 'jquery'
@@ -236,6 +249,7 @@
return describe('comparator', function(describe, it) {
it('live-onadd', function() {
var actual, expected;
+
actual = queryEngine.createLiveCollection().setComparator({
position: -1
}).add(docs.models);
@@ -244,6 +258,7 @@
});
return it('live-onchange', function() {
var actual, expected;
+
actual = queryEngine.createLiveCollection().setComparator({
position: -1
}).add(docs.models);
@@ -259,6 +274,7 @@
describe('sort', function(describe, it) {
var collectionName, docs, _results;
+
_results = [];
for (collectionName in store) {
if (!__hasProp.call(store, collectionName)) continue;
View
1,613 out/vendor/backbone.js
@@ -1,42 +1,1571 @@
-// Backbone.js 0.9.10
-
-// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
-// Backbone may be freely distributed under the MIT license.
-// For all details and documentation:
-// http://backbonejs.org
-(function(){var n=this,B=n.Backbone,h=[],C=h.push,u=h.slice,D=h.splice,g;g="undefined"!==typeof exports?exports:n.Backbone={};g.VERSION="0.9.10";var f=n._;!f&&"undefined"!==typeof require&&(f=require("underscore"));g.$=n.jQuery||n.Zepto||n.ender;g.noConflict=function(){n.Backbone=B;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var v=/\s+/,q=function(a,b,c,d){if(!c)return!0;if("object"===typeof c)for(var e in c)a[b].apply(a,[e,c[e]].concat(d));else if(v.test(c)){c=c.split(v);e=0;for(var f=c.length;e<
-f;e++)a[b].apply(a,[c[e]].concat(d))}else return!0},w=function(a,b){var c,d=-1,e=a.length;switch(b.length){case 0:for(;++d<e;)(c=a[d]).callback.call(c.ctx);break;case 1:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0]);break;case 2:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0],b[1]);break;case 3:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0],b[1],b[2]);break;default:for(;++d<e;)(c=a[d]).callback.apply(c.ctx,b)}},h=g.Events={on:function(a,b,c){if(!q(this,"on",a,[b,c])||!b)return this;this._events||(this._events=
-{});(this._events[a]||(this._events[a]=[])).push({callback:b,context:c,ctx:c||this});return this},once:function(a,b,c){if(!q(this,"once",a,[b,c])||!b)return this;var d=this,e=f.once(function(){d.off(a,e);b.apply(this,arguments)});e._callback=b;this.on(a,e,c);return this},off:function(a,b,c){var d,e,t,g,j,l,k,h;if(!this._events||!q(this,"off",a,[b,c]))return this;if(!a&&!b&&!c)return this._events={},this;g=a?[a]:f.keys(this._events);j=0;for(l=g.length;j<l;j++)if(a=g[j],d=this._events[a]){t=[];if(b||
-c){k=0;for(h=d.length;k<h;k++)e=d[k],(b&&b!==e.callback&&b!==e.callback._callback||c&&c!==e.context)&&t.push(e)}this._events[a]=t}return this},trigger:function(a){if(!this._events)return this;var b=u.call(arguments,1);if(!q(this,"trigger",a,b))return this;var c=this._events[a],d=this._events.all;c&&w(c,b);d&&w(d,arguments);return this},listenTo:function(a,b,c){var d=this._listeners||(this._listeners={}),e=a._listenerId||(a._listenerId=f.uniqueId("l"));d[e]=a;a.on(b,"object"===typeof b?this:c,this);
-return this},stopListening:function(a,b,c){var d=this._listeners;if(d){if(a)a.off(b,"object"===typeof b?this:c,this),!b&&!c&&delete d[a._listenerId];else{"object"===typeof b&&(c=this);for(var e in d)d[e].off(b,c,this);this._listeners={}}return this}}};h.bind=h.on;h.unbind=h.off;f.extend(g,h);var r=g.Model=function(a,b){var c,d=a||{};this.cid=f.uniqueId("c");this.attributes={};b&&b.collection&&(this.collection=b.collection);b&&b.parse&&(d=this.parse(d,b)||{});if(c=f.result(this,"defaults"))d=f.defaults({},
-d,c);this.set(d,b);this.changed={};this.initialize.apply(this,arguments)};f.extend(r.prototype,h,{changed:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},sync:function(){return g.sync.apply(this,arguments)},get:function(a){return this.attributes[a]},escape:function(a){return f.escape(this.get(a))},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e,g,p,j,l,k;if(null==a)return this;"object"===typeof a?(e=a,c=b):(e={})[a]=b;c||(c={});
-if(!this._validate(e,c))return!1;g=c.unset;p=c.silent;a=[];j=this._changing;this._changing=!0;j||(this._previousAttributes=f.clone(this.attributes),this.changed={});k=this.attributes;l=this._previousAttributes;this.idAttribute in e&&(this.id=e[this.idAttribute]);for(d in e)b=e[d],f.isEqual(k[d],b)||a.push(d),f.isEqual(l[d],b)?delete this.changed[d]:this.changed[d]=b,g?delete k[d]:k[d]=b;if(!p){a.length&&(this._pending=!0);b=0;for(d=a.length;b<d;b++)this.trigger("change:"+a[b],this,k[a[b]],c)}if(j)return this;
-if(!p)for(;this._pending;)this._pending=!1,this.trigger("change",this,c);this._changing=this._pending=!1;return this},unset:function(a,b){return this.set(a,void 0,f.extend({},b,{unset:!0}))},clear:function(a){var b={},c;for(c in this.attributes)b[c]=void 0;return this.set(b,f.extend({},a,{unset:!0}))},hasChanged:function(a){return null==a?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._changing?
-this._previousAttributes:this.attributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return null==a||!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=a.success;a.success=function(a,d,e){if(!a.set(a.parse(d,e),e))return!1;b&&b(a,d,e)};return this.sync("read",this,a)},save:function(a,b,c){var d,e,g=this.attributes;
-null==a||"object"===typeof a?(d=a,c=b):(d={})[a]=b;if(d&&(!c||!c.wait)&&!this.set(d,c))return!1;c=f.extend({validate:!0},c);if(!this._validate(d,c))return!1;d&&c.wait&&(this.attributes=f.extend({},g,d));void 0===c.parse&&(c.parse=!0);e=c.success;c.success=function(a,b,c){a.attributes=g;var k=a.parse(b,c);c.wait&&(k=f.extend(d||{},k));if(f.isObject(k)&&!a.set(k,c))return!1;e&&e(a,b,c)};a=this.isNew()?"create":c.patch?"patch":"update";"patch"===a&&(c.attrs=d);a=this.sync(a,this,c);d&&c.wait&&(this.attributes=
-g);return a},destroy:function(a){a=a?f.clone(a):{};var b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};a.success=function(a,b,e){(e.wait||a.isNew())&&d();c&&c(a,b,e)};if(this.isNew())return a.success(this,null,a),!1;var e=this.sync("delete",this,a);a.wait||d();return e},url:function(){var a=f.result(this,"urlRoot")||f.result(this.collection,"url")||x();return this.isNew()?a:a+("/"===a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},
-isNew:function(){return null==this.id},isValid:function(a){return!this.validate||!this.validate(this.attributes,a)},_validate:function(a,b){if(!b.validate||!this.validate)return!0;a=f.extend({},this.attributes,a);var c=this.validationError=this.validate(a,b)||null;if(!c)return!0;this.trigger("invalid",this,c,b||{});return!1}});var s=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);void 0!==b.comparator&&(this.comparator=b.comparator);this.models=[];this._reset();this.initialize.apply(this,
-arguments);a&&this.reset(a,f.extend({silent:!0},b))};f.extend(s.prototype,h,{model:r,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},sync:function(){return g.sync.apply(this,arguments)},add:function(a,b){a=f.isArray(a)?a.slice():[a];b||(b={});var c,d,e,g,p,j,l,k,h,m;l=[];k=b.at;h=this.comparator&&null==k&&!1!=b.sort;m=f.isString(this.comparator)?this.comparator:null;c=0;for(d=a.length;c<d;c++)(e=this._prepareModel(g=a[c],b))?(p=this.get(e))?b.merge&&(p.set(g===
-e?e.attributes:g,b),h&&(!j&&p.hasChanged(m))&&(j=!0)):(l.push(e),e.on("all",this._onModelEvent,this),this._byId[e.cid]=e,null!=e.id&&(this._byId[e.id]=e)):this.trigger("invalid",this,g,b);l.length&&(h&&(j=!0),this.length+=l.length,null!=k?D.apply(this.models,[k,0].concat(l)):C.apply(this.models,l));j&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=l.length;c<d;c++)(e=l[c]).trigger("add",e,this,b);j&&this.trigger("sort",this,b);return this},remove:function(a,b){a=f.isArray(a)?a.slice():[a];
-b||(b={});var c,d,e,g;c=0;for(d=a.length;c<d;c++)if(g=this.get(a[c]))delete this._byId[g.id],delete this._byId[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:this.length},b));return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},
-b));return a},shift:function(a){var b=this.at(0);this.remove(b,a);return b},slice:function(a,b){return this.models.slice(a,b)},get:function(a){if(null!=a)return this._idAttr||(this._idAttr=this.model.prototype.idAttribute),this._byId[a.id||a.cid||a[this._idAttr]||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){if(!this.comparator)throw Error("Cannot sort a set without a comparator");
-a||(a={});f.isString(this.comparator)||1===this.comparator.length?this.models=this.sortBy(this.comparator,this):this.models.sort(f.bind(this.comparator,this));a.silent||this.trigger("sort",this,a);return this},pluck:function(a){return f.invoke(this.models,"get",a)},update:function(a,b){b=f.extend({add:!0,merge:!0,remove:!0},b);b.parse&&(a=this.parse(a,b));var c,d,e,g,h=[],j=[],l={};f.isArray(a)||(a=a?[a]:[]);if(b.add&&!b.remove)return this.add(a,b);d=0;for(e=a.length;d<e;d++)c=a[d],g=this.get(c),
-b.remove&&g&&(l[g.cid]=!0),(b.add&&!g||b.merge&&g)&&h.push(c);if(b.remove){d=0;for(e=this.models.length;d<e;d++)c=this.models[d],l[c.cid]||j.push(c)}j.length&&this.remove(j,b);h.length&&this.add(h,b);return this},reset:function(a,b){b||(b={});b.parse&&(a=this.parse(a,b));for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);b.previousModels=this.models.slice();this._reset();a&&this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=
-a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=a.success;a.success=function(a,d,e){a[e.update?"update":"reset"](d,e);b&&b(a,d,e)};return this.sync("read",this,a)},create:function(a,b){b=b?f.clone(b):{};if(!(a=this._prepareModel(a,b)))return!1;b.wait||this.add(a,b);var c=this,d=b.success;b.success=function(a,b,f){f.wait&&c.add(a,f);d&&d(a,b,f)};a.save(null,b);return a},parse:function(a){return a},clone:function(){return new this.constructor(this.models)},_reset:function(){this.length=0;this.models.length=
-0;this._byId={}},_prepareModel:function(a,b){if(a instanceof r)return a.collection||(a.collection=this),a;b||(b={});b.collection=this;var c=new this.model(a,b);return!c._validate(a,b)?!1:c},_removeReference:function(a){this===a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"===a||"remove"===a)&&c!==this||("destroy"===a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],null!=b.id&&(this._byId[b.id]=
-b)),this.trigger.apply(this,arguments))},sortedIndex:function(a,b,c){b||(b=this.comparator);var d=f.isFunction(b)?b:function(a){return a.get(b)};return f.sortedIndex(this.models,a,d,c)}});f.each("forEach each map collect reduce foldl inject reduceRight foldr find detect filter select reject every all some any include contains invoke max min toArray size first head take initial rest tail drop last without indexOf shuffle lastIndexOf isEmpty chain".split(" "),function(a){s.prototype[a]=function(){var b=
-u.call(arguments);b.unshift(this.models);return f[a].apply(f,b)}});f.each(["groupBy","countBy","sortBy"],function(a){s.prototype[a]=function(b,c){var d=f.isFunction(b)?b:function(a){return a.get(b)};return f[a](this.models,d,c)}});var y=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},E=/\((.*?)\)/g,F=/(\(\?)?:\w+/g,G=/\*\w+/g,H=/[\-{}\[\]+?.,\\\^$|#\s]/g;f.extend(y.prototype,h,{initialize:function(){},route:function(a,b,c){f.isRegExp(a)||
-(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));this.trigger("route",b,d);g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b);return this},_bindRoutes:function(){if(this.routes)for(var a,b=f.keys(this.routes);null!=(a=b.pop());)this.route(a,this.routes[a])},_routeToRegExp:function(a){a=a.replace(H,"\\$&").replace(E,"(?:$1)?").replace(F,
-function(a,c){return c?a:"([^/]+)"}).replace(G,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl");"undefined"!==typeof window&&(this.location=window.location,this.history=window.history)},z=/^[#\/]|\s+$/g,I=/^\/+|\/+$/g,J=/msie [\w.]+/,K=/\/$/;m.started=!1;f.extend(m.prototype,h,{interval:50,getHash:function(a){return(a=(a||this).location.href.match(/#(.*)$/))?a[1]:""},getFragment:function(a,
-b){if(null==a)if(this._hasPushState||!this._wantsHashChange||b){a=this.location.pathname;var c=this.root.replace(K,"");a.indexOf(c)||(a=a.substr(c.length))}else a=this.getHash();return a.replace(z,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this.root=this.options.root;this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!(!this.options.pushState||
-!this.history||!this.history.pushState);a=this.getFragment();var b=document.documentMode,b=J.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b);this.root=("/"+this.root+"/").replace(I,"/");b&&this._wantsHashChange&&(this.iframe=g.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a));if(this._hasPushState)g.$(window).on("popstate",this.checkUrl);else if(this._wantsHashChange&&"onhashchange"in window&&!b)g.$(window).on("hashchange",this.checkUrl);
-else this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,this.interval));this.fragment=a;a=this.location;b=a.pathname.replace(/[^\/]$/,"$&/")===this.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),this.location.replace(this.root+this.location.search+"#"+this.fragment),!0;this._wantsPushState&&(this._hasPushState&&b&&a.hash)&&(this.fragment=this.getHash().replace(z,""),this.history.replaceState({},document.title,
-this.root+this.fragment+a.search));if(!this.options.silent)return this.loadUrl()},stop:function(){g.$(window).off("popstate",this.checkUrl).off("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a===this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a===this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},
-loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};a=this.getFragment(a||"");if(this.fragment!==a){this.fragment=a;var c=this.root+a;if(this._hasPushState)this.history[b.replace?"replaceState":"pushState"]({},document.title,c);else if(this._wantsHashChange)this._updateHash(this.location,a,b.replace),this.iframe&&a!==this.getFragment(this.getHash(this.iframe))&&
-(b.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,a,b.replace));else return this.location.assign(c);b.trigger&&this.loadUrl(a)}},_updateHash:function(a,b,c){c?(c=a.href.replace(/(javascript:|#).*$/,""),a.replace(c+"#"+b)):a.hash="#"+b}});g.history=new m;var A=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},L=/^(\S+)\s*(.*)$/,M="model collection el id attributes className tagName events".split(" ");
-f.extend(A.prototype,h,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();this.stopListening();return this},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof g.$?a:g.$(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=f.result(this,"events"))){this.undelegateEvents();for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);
-if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(L),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);if(""===d)this.$el.on(e,c);else this.$el.on(e,d,c)}}},undelegateEvents:function(){this.$el.off(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},f.result(this,"options"),a));f.extend(this,f.pick(a,M));this.options=a},_ensureElement:function(){if(this.el)this.setElement(f.result(this,"el"),!1);else{var a=f.extend({},f.result(this,"attributes"));
-this.id&&(a.id=f.result(this,"id"));this.className&&(a["class"]=f.result(this,"className"));a=g.$("<"+f.result(this,"tagName")+">").attr(a);this.setElement(a,!1)}}});var N={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=N[a];f.defaults(c||(c={}),{emulateHTTP:g.emulateHTTP,emulateJSON:g.emulateJSON});var e={type:d,dataType:"json"};c.url||(e.url=f.result(b,"url")||x());if(null==c.data&&b&&("create"===a||"update"===a||"patch"===a))e.contentType="application/json",
-e.data=JSON.stringify(c.attrs||b.toJSON(c));c.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(c.emulateHTTP&&("PUT"===d||"DELETE"===d||"PATCH"===d)){e.type="POST";c.emulateJSON&&(e.data._method=d);var h=c.beforeSend;c.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d);if(h)return h.apply(this,arguments)}}"GET"!==e.type&&!c.emulateJSON&&(e.processData=!1);var m=c.success;c.success=function(a){m&&m(b,a,c);b.trigger("sync",b,a,c)};
-var j=c.error;c.error=function(a){j&&j(b,a,c);b.trigger("error",b,a,c)};a=c.xhr=g.ajax(f.extend(e,c));b.trigger("request",b,a,c);return a};g.ajax=function(){return g.$.ajax.apply(g.$,arguments)};r.extend=s.extend=y.extend=A.extend=m.extend=function(a,b){var c=this,d;d=a&&f.has(a,"constructor")?a.constructor:function(){return c.apply(this,arguments)};f.extend(d,c,b);var e=function(){this.constructor=d};e.prototype=c.prototype;d.prototype=new e;a&&f.extend(d.prototype,a);d.__super__=c.prototype;return d};
-var x=function(){throw Error('A "url" property or function must be specified');}}).call(this);
+// Backbone.js 1.0.0
+
+// (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+
+(function(){
+
+ // Initial Setup
+ // -------------
+
+ // Save a reference to the global object (`window` in the browser, `exports`
+ // on the server).
+ var root = this;
+
+ // Save the previous value of the `Backbone` variable, so that it can be
+ // restored later on, if `noConflict` is used.
+ var previousBackbone = root.Backbone;
+
+ // Create local references to array methods we'll want to use later.
+ 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 the browser and the server.
+ var Backbone;
+ if (typeof exports !== 'undefined') {
+ Backbone = exports;
+ } else {
+ Backbone = root.Backbone = {};
+ }
+
+ // Current version of the library. Keep in sync with `package.json`.
+ Backbone.VERSION = '1.0.0';
+
+ // Require Underscore, if we're on the server, and it's not already present.
+ var _ = root._;
+ if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
+
+ // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
+ // the `$` variable.
+ Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;
+
+ // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
+ // to its previous owner. Returns a reference to this Backbone object.
+ Backbone.noConflict = function() {
+ root.Backbone = previousBackbone;
+ return this;
+ };
+
+ // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
+ // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
+ // set a `X-Http-Method-Override` header.
+ Backbone.emulateHTTP = false;
+
+ // Turn on `emulateJSON` to support legacy servers that can't deal with direct
+ // `application/json` requests ... will encode the body as
+ // `application/x-www-form-urlencoded` instead and will send the model in a
+ // form param named `model`.
+ Backbone.emulateJSON = false;
+
+ // Backbone.Events
+ // ---------------
+
+ // 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.
+ //
+ // var object = {};
+ // _.extend(object, Backbone.Events);
+ // object.on('expand', function(){ alert('expanded'); });
+ // object.trigger('expand');
+ //
+ var Events = Backbone.Events = {
+
+ // Bind an event 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 events = this._events[name] || (this._events[name] = []);
+ events.push({callback: callback, context: context, ctx: context || this});
+ return this;
+ },
+
+ // Bind an event 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;
+ return this.on(name, once, context);
+ },
+
+ // 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
+ // callbacks for all events.
+ off: function(name, callback, context) {
+ var retain, 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;
+ }
+
+ names = name ? [name] : _.keys(this._events);
+ for (i = 0, l = names.length; i < l; i++) {
+ name = names[i];
+ if (events = this._events[name]) {
+ this._events[name] = retain = [];
+ if (callback || context) {
+ for (j = 0, k = events.length; j < k; j++) {
+ ev = events[j];
+ if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
+ (context && context !== ev.context)) {
+ retain.push(ev);
+ }
+ }
+ }
+ if (!retain.length) delete this._events[name];
+ }
+ }
+
+ return this;
+ },
+
+ // Trigger one or many events, firing all bound callbacks. Callbacks are
+ // 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(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(events, args);
+ if (allEvents) triggerEvents(allEvents, arguments);
+ return this;
+ },
+
+ // Tell this object to stop listening to either specific events ... or
+ // to every object it's currently listening to.
+ stopListening: function(obj, name, callback) {
+ var listeners = this._listeners;
+ if (!listeners) return this;
+ var deleteListener = !name && !callback;
+ if (typeof name === 'object') callback = this;
+ if (obj) (listeners = {})[obj._listenerId] = obj;
+ for (var id in listeners) {
+ listeners[id].off(name, callback, this);
+ if (deleteListener) delete this._listeners[id];
+ }
+ return this;
+ }
+
+ };
+
+ // 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;
+
+ // Handle event maps.
+ if (typeof name === 'object') {
+ for (var key in name) {
+ obj[action].apply(obj, [key, name[key]].concat(rest));
+ }
+ return false;
+ }
+
+ // Handle space separated event names.
+ 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));
+ }
+ return false;
+ }
+
+ return true;
+ };
+
+ // A difficult-to-believe, but optimized internal dispatch function for
+ // triggering events. Tries to keep the usual cases speedy (most internal
+ // Backbone events have 3 arguments).
+ var triggerEvents = function(events, args) {
+ var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
+ 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, a1); return;
+ case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
+ case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
+ default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
+ }
+ };
+
+ var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
+
+ // Inversion-of-control versions of `on` and `once`. Tell *this* object to
+ // listen to an event in another object ... keeping track of what it's
+ // listening to.
+ _.each(listenMethods, function(implementation, method) {
+ Events[method] = function(obj, name, callback) {
+ var listeners = this._listeners || (this._listeners = {});
+ var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
+ listeners[id] = obj;
+ if (typeof name === 'object') callback = this;
+ obj[implementation](name, callback, this);
+ 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
+ // --------------
+
+ // Backbone **Models** are the basic data object in the framework --
+ // frequently representing a row in a table in a database on your server.
+ // A discrete chunk of data and a bunch of useful, related methods for
+ // performing computations and transformations on that data.
+
+ // Create a new model with the specified attributes. A client id (`cid`)
+ // is automatically generated and assigned for you.
+ var Model = Backbone.Model = function(attributes, options) {
+ var defaults;
+ var attrs = attributes || {};
+ options || (options = {});
+ this.cid = _.uniqueId('c');
+ this.attributes = {};
+ _.extend(this, _.pick(options, modelOptions));
+ if (options.parse) attrs = this.parse(attrs, options) || {};
+ if (defaults = _.result(this, 'defaults')) {
+ attrs = _.defaults({}, attrs, defaults);
+ }
+ this.set(attrs, options);
+ this.changed = {};
+ this.initialize.apply(this, arguments);
+ };
+
+ // A list of options to be attached directly to the model, if provided.
+ var modelOptions = ['url', 'urlRoot', 'collection'];
+
+ // Attach all inheritable methods to the Model prototype.
+ _.extend(Model.prototype, Events, {
+
+ // A hash of attributes whose current and previous value differ.
+ changed: null,
+
+ // The value returned during the last failed validation.
+ validationError: null,
+
+ // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+ // CouchDB users may want to set this to `"_id"`.
+ idAttribute: 'id',
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // Return a copy of the model's `attributes` object.
+ toJSON: function(options) {
+ return _.clone(this.attributes);
+ },
+
+ // Proxy `Backbone.sync` by default -- but override this if you need
+ // custom syncing semantics for *this* particular model.
+ sync: function() {
+ return Backbone.sync.apply(this, arguments);
+ },
+
+ // Get the value of an attribute.
+ get: function(attr) {
+ return this.attributes[attr];
+ },
+
+ // Get the HTML-escaped value of an attribute.
+ escape: function(attr) {
+ return _.escape(this.get(attr));
+ },
+
+ // Returns `true` if the attribute contains a value that is not null
+ // or undefined.
+ has: function(attr) {
+ return this.get(attr) != null;
+ },
+
+ // Set a hash of model attributes on the object, firing `"change"`. This is
+ // the core primitive operation of a model, updating the data and notifying
+ // anyone who needs to know about the change in state. The heart of the beast.
+ set: function(key, val, options) {
+ var attr, attrs, unset, changes, silent, changing, prev, current;
+ if (key == null) return this;
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ if (typeof key === 'object') {
+ attrs = key;
+ options = val;
+ } else {
+ (attrs = {})[key] = val;
+ }
+
+ options || (options = {});
+
+ // Run validation.
+ if (!this._validate(attrs, options)) return false;
+
+ // Extract attributes and options.
+ unset = options.unset;
+ silent = options.silent;
+ changes = [];
+ changing = this._changing;
+ this._changing = true;
+
+ if (!changing) {
+ this._previousAttributes = _.clone(this.attributes);
+ this.changed = {};
+ }
+ current = this.attributes, prev = this._previousAttributes;
+
+ // Check for changes of `id`.
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+ // For each `set` attribute, update or delete the current value.
+ for (attr in attrs) {
+ val = attrs[attr];
+ if (!_.isEqual(current[attr], val)) changes.push(attr);
+ if (!_.isEqual(prev[attr], val)) {
+ this.changed[attr] = val;
+ } else {
+ delete this.changed[attr];
+ }
+ unset ? delete current[attr] : current[attr] = val;
+ }
+
+ // Trigger all relevant attribute changes.
+ if (!silent) {
+ if (changes.length) this._pending = true;
+ for (var i = 0, l = changes.length; i < l; i++) {
+ this.trigger('change:' + changes[i], this, current[changes[i]], options);
+ }
+ }
+
+ // You might be wondering why there's a `while` loop here. Changes can
+ // be recursively nested within `"change"` events.
+ if (changing) return this;
+ if (!silent) {
+ while (this._pending) {
+ this._pending = false;
+ this.trigger('change', this, options);
+ }
+ }
+ this._pending = false;
+ this._changing = false;
+ return this;
+ },
+
+ // Remove an attribute from the model, firi