diff --git a/bi/core.js b/bi/core.js index 55eea25e4..0ecc286ae 100644 --- a/bi/core.js +++ b/bi/core.js @@ -49,9 +49,9 @@ BI.Factory = { // } return view; } -};(function(root, factory) { - root.BI = factory(root, root.BI || {}, root._, (root.jQuery || root.$)); -}(this, function(root, BI, _, $) { +};(function (root, factory) { + root.BI = factory(root, root.BI || {}, root._, (root.jQuery || root.$)); +}(this, function (root, BI, _, $) { var previousBI = root.BI; @@ -68,7 +68,7 @@ BI.Factory = { // Runs BI.js in *noConflict* mode, returning the `BI` variable // to its previous owner. Returns a reference to this BI object. - BI.noConflict = function() { + BI.noConflict = function () { root.BI = previousBI; return this; }; @@ -101,7 +101,7 @@ BI.Factory = { // Bind an event to a `callback` function. Passing `"all"` will bind // the callback to all events fired. - on: function(name, callback, context) { + 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] = []); @@ -111,10 +111,10 @@ BI.Factory = { // 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) { + once: function (name, callback, context) { if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; var self = this; - var once = _.once(function() { + var once = _.once(function () { self.off(name, once); callback.apply(this, arguments); }); @@ -126,7 +126,7 @@ BI.Factory = { // 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) { + off: function (name, callback, context) { if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; // Remove all callbacks for all events. @@ -157,7 +157,7 @@ BI.Factory = { callback && callback !== event.callback && callback !== event.callback._callback || context && context !== event.context - ) { + ) { remaining.push(event); } } @@ -173,11 +173,15 @@ BI.Factory = { return this; }, + un: function () { + this.off.apply(this, arguments); + }, + // 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) { + trigger: function (name) { if (!this._events) return this; var args = slice.call(arguments, 1); if (!eventsApi(this, 'trigger', name, args)) return this; @@ -188,10 +192,14 @@ BI.Factory = { return this; }, + fireEvent: function () { + this.trigger.apply(this, arguments); + }, + // 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. - listenTo: function(obj, name, callback) { + listenTo: function (obj, name, callback) { var listeningTo = this._listeningTo || (this._listeningTo = {}); var id = obj._listenId || (obj._listenId = _.uniqueId('l')); listeningTo[id] = obj; @@ -200,7 +208,7 @@ BI.Factory = { return this; }, - listenToOnce: function(obj, name, callback) { + listenToOnce: function (obj, name, callback) { if (typeof name === 'object') { for (var event in name) this.listenToOnce(obj, event, name[event]); return this; @@ -213,7 +221,7 @@ BI.Factory = { return this; } if (!callback) return this; - var once = _.once(function() { + var once = _.once(function () { this.stopListening(obj, name, once); callback.apply(this, arguments); }); @@ -223,7 +231,7 @@ BI.Factory = { // Tell this object to stop listening to either specific events ... or // to every object it's currently listening to. - stopListening: function(obj, name, callback) { + stopListening: function (obj, name, callback) { var listeningTo = this._listeningTo; if (!listeningTo) return this; var remove = !name && !callback; @@ -245,7 +253,7 @@ BI.Factory = { // 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) { + var eventsApi = function (obj, action, name, rest) { if (!name) return true; // Handle event maps. @@ -271,19 +279,29 @@ BI.Factory = { // A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // BI events have 3 arguments). - var triggerEvents = function(events, args) { + 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); return; + 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); + return; } }; // Aliases for backwards compatibility. - Events.bind = Events.on; + Events.bind = Events.on; Events.unbind = Events.off; // Allow the `BI` object to serve as a global event bus, for folks who @@ -300,7 +318,7 @@ BI.Factory = { // Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. - var M = BI.M = function(attributes, options) { + var M = BI.M = function (attributes, options) { var attrs = attributes || {}; options = options || {}; this.cid = _.uniqueId('c'); @@ -329,40 +347,47 @@ BI.Factory = { // CouchDB users may want to set this to `"_id"`. idAttribute: 'ID', - _defaultConfig: function(){return {}}, + _defaultConfig: function () { + return {} + }, + + init: function () { + }, // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + this.init(); + }, // Return a copy of the model's `attributes` object. - toJSON: function(options) { + toJSON: function (options) { return _.clone(this.attributes); }, // Proxy `BI.sync` by default -- but override this if you need // custom syncing semantics for *this* particular model. - sync: function() { + sync: function () { return BI.sync.apply(this, arguments); }, // Get the value of an attribute. - get: function(attr) { + get: function (attr) { return this.attributes[attr]; }, // Get the HTML-escaped value of an attribute. - escape: function(attr) { + 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) { + has: function (attr) { return _.has(this.attributes, attr); }, // Special-cased proxy to underscore's `_.matches` method. - matches: function(attrs) { + matches: function (attrs) { var keys = _.keys(attrs), length = keys.length; var obj = Object(this.attributes); for (var i = 0; i < length; i++) { @@ -375,7 +400,7 @@ BI.Factory = { // 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) { + set: function (key, val, options) { var attr, attrs, unset, changes, silent, changing, changed, prev, current; if (key == null) return this; @@ -393,11 +418,11 @@ BI.Factory = { if (!this._validate(attrs, options)) return false; // Extract attributes and options. - unset = options.unset; - silent = options.silent; - changes = []; - changing = this._changing; - this._changing = true; + unset = options.unset; + silent = options.silent; + changes = []; + changing = this._changing; + this._changing = true; if (!changing) { this._previousAttributes = _.clone(this.attributes); @@ -447,12 +472,12 @@ BI.Factory = { // Remove an attribute from the model, firing `"change"`. `unset` is a noop // if the attribute doesn't exist. - unset: function(attr, options) { + unset: function (attr, options) { return this.set(attr, void 0, _.extend({}, options, {unset: true})); }, // Clear all attributes on the model, firing `"change"`. - clear: function(options) { + clear: function (options) { var attrs = {}; for (var key in this.attributes) attrs[key] = void 0; return this.set(attrs, _.extend({}, options, {unset: true})); @@ -460,7 +485,7 @@ BI.Factory = { // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. - hasChanged: function(attr) { + hasChanged: function (attr) { if (attr == null) return !_.isEmpty(this.changed); return _.has(this.changed, attr); }, @@ -471,7 +496,7 @@ BI.Factory = { // persisted to the server. Unset attributes will be set to undefined. // You can also pass an attributes object to diff against the model, // determining if there *would be* a change. - changedAttributes: function(diff) { + changedAttributes: function (diff) { if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; var val, changed = false; var old = this._changing ? this._previousAttributes : this.attributes; @@ -484,27 +509,27 @@ BI.Factory = { // Get the previous value of an attribute, recorded at the time the last // `"change"` event was fired. - previous: function(attr) { + previous: function (attr) { if (attr == null || !this._previousAttributes) return null; return this._previousAttributes[attr]; }, // Get all of the attributes of the model at the time of the previous // `"change"` event. - previousAttributes: function() { + previousAttributes: function () { return _.clone(this._previousAttributes); }, // Fetch the model from the server. If the server's representation of the // model differs from its current attributes, they will be overridden, // triggering a `"change"` event. - fetch: function(options) { + fetch: function (options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; - options.success = function(resp) { - if(!options.noset) { + options.success = function (resp) { + if (!options.noset) { if (!model.set(model.parse(resp, options), options)) return false; } if (success) success(resp, model, options); @@ -517,7 +542,7 @@ BI.Factory = { // Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. - save: function(key, val, options) { + save: function (key, val, options) { var attrs, method, xhr, attributes = this.attributes; // Handle both `"key", value` and `{key: value}` -style arguments. @@ -549,7 +574,7 @@ BI.Factory = { if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; - options.success = function(resp) { + options.success = function (resp) { // Ensure attributes are restored during synchronous saves. model.attributes = attributes; var serverAttrs = model.parse(resp, options); @@ -576,17 +601,17 @@ BI.Factory = { // Destroy this model on the server if it was already persisted. // Optimistically removes the model from its collection, if it has one. // If `wait: true` is passed, waits for the server to respond before removal. - destroy: function(options) { + destroy: function (options) { options = options ? _.clone(options) : {}; var model = this; var success = options.success; - var destroy = function() { + var destroy = function () { model.stopListening(); model.trigger('destroy', model.collection, model, options); }; - options.success = function(resp) { + options.success = function (resp) { if (options.wait || model.isNew()) destroy(); if (success) success(resp, model, options); if (!model.isNew()) model.trigger('sync', resp, model, options).trigger('delete', resp, model, options); @@ -606,7 +631,7 @@ BI.Factory = { // Default URL for the model's representation on the server -- if you're // using BI's restful methods, override this to change the endpoint // that will be called. - url: function() { + url: function () { var base = _.result(this.collection, 'url'); if (this.isNew()) return base; @@ -615,28 +640,28 @@ BI.Factory = { // **parse** converts a response into the hash of attributes to be `set` on // the model. The default implementation is just to pass the response along. - parse: function(resp, options) { + parse: function (resp, options) { return resp; }, // Create a new model with identical attributes to this one. - clone: function() { + clone: function () { return new this.constructor(this.attributes); }, // A model is new if it has never been saved to the server, and lacks an id. - isNew: function() { + isNew: function () { return !this.has(this.idAttribute); }, // Check if the model is currently in a valid state. - isValid: function(options) { - return this._validate({}, _.extend(options || {}, { validate: true })); + isValid: function (options) { + return this._validate({}, _.extend(options || {}, {validate: true})); }, // Run validation against the next complete set of model attributes, // returning `true` if all is well. Otherwise, fire an `"invalid"` event. - _validate: function(attrs, options) { + _validate: function (attrs, options) { if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); var error = this.validationError = this.validate(attrs, options) || null; @@ -651,9 +676,9 @@ BI.Factory = { var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit', 'chain', 'isEmpty']; // Mix in each Underscore method as a proxy to `M#attributes`. - _.each(modelMethods, function(method) { + _.each(modelMethods, function (method) { if (!_[method]) return; - M.prototype[method] = function() { + M.prototype[method] = function () { var args = slice.call(arguments); args.unshift(this.attributes); return _[method].apply(_, args); @@ -673,7 +698,7 @@ BI.Factory = { // Create a new **Collection**, perhaps to contain a specific type of `model`. // If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. - var Collection = BI.Collection = function(models, options) { + var Collection = BI.Collection = function (models, options) { this.options = options = options || {}; if (options.model) this.model = options.model; if (options.comparator !== void 0) this.comparator = options.comparator; @@ -695,26 +720,29 @@ BI.Factory = { // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + }, // The JSON representation of a Collection is an array of the // models' attributes. - toJSON: function(options) { - return this.map(function(model){ return model.toJSON(options); }); + toJSON: function (options) { + return this.map(function (model) { + return model.toJSON(options); + }); }, // Proxy `BI.sync` by default. - sync: function() { + sync: function () { return BI.sync.apply(this, arguments); }, // Add a model, or list of models to the set. - add: function(models, options) { + add: function (models, options) { return this.set(models, _.extend({merge: false}, options, addOptions)); }, // Remove a model, or a list of models from the set. - remove: function(models, options) { + remove: function (models, options) { var singular = !_.isArray(models); models = singular ? [models] : _.clone(models); options || (options = {}); @@ -740,7 +768,7 @@ BI.Factory = { // removing models that are no longer present, and merging models that // already exist in the collection, as necessary. Similar to **M#set**, // the core operation for updating the data contained by the collection. - set: function(models, options) { + set: function (models, options) { options = _.defaults({}, options, setOptions); if (options.parse) models = this.parse(models, options); var singular = !_.isArray(models); @@ -841,7 +869,7 @@ BI.Factory = { // you can reset the entire set with a new list of models, without firing // any granular `add` or `remove` events. Fires `reset` when finished. // Useful for bulk operations and optimizations. - reset: function(models, options) { + reset: function (models, options) { options = options ? _.clone(options) : {}; for (var i = 0, length = this.models.length; i < length; i++) { this._removeReference(this.models[i], options); @@ -854,66 +882,66 @@ BI.Factory = { }, // Add a model to the end of the collection. - push: function(model, options) { + push: function (model, options) { return this.add(model, _.extend({at: this.length}, options)); }, // Remove a model from the end of the collection. - pop: function(options) { + pop: function (options) { var model = this.at(this.length - 1); this.remove(model, options); return model; }, // Add a model to the beginning of the collection. - unshift: function(model, options) { + unshift: function (model, options) { return this.add(model, _.extend({at: 0}, options)); }, // Remove a model from the beginning of the collection. - shift: function(options) { + shift: function (options) { var model = this.at(0); this.remove(model, options); return model; }, // Slice out a sub-array of models from the collection. - slice: function() { + slice: function () { return slice.apply(this.models, arguments); }, // Get a model from the set by id. - get: function(obj) { + get: function (obj) { if (obj == null) return void 0; var id = this.modelId(this._isModel(obj) ? obj.attributes : obj); return this._byId[obj] || this._byId[id] || this._byId[obj.cid]; }, // Get the model at the given index. - at: function(index) { + at: function (index) { if (index < 0) index += this.length; return this.models[index]; }, // Return models with matching attributes. Useful for simple cases of // `filter`. - where: function(attrs, first) { + where: function (attrs, first) { var matches = _.matches(attrs); - return this[first ? 'find' : 'filter'](function(model) { + return this[first ? 'find' : 'filter'](function (model) { return matches(model.attributes); }); }, // Return the first model with matching attributes. Useful for simple cases // of `find`. - findWhere: function(attrs) { + findWhere: function (attrs) { return this.where(attrs, true); }, // Force the collection to re-sort itself. You don't need to call this under // normal circumstances, as the set will maintain sort order as each item // is added. - sort: function(options) { + sort: function (options) { if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); options || (options = {}); @@ -929,19 +957,19 @@ BI.Factory = { }, // Pluck an attribute from each model in the collection. - pluck: function(attr) { + pluck: function (attr) { return _.invoke(this.models, 'get', attr); }, // Fetch the default set of models for this collection, resetting the // collection when they arrive. If `reset: true` is passed, the response // data will be passed through the `reset` method instead of `set`. - fetch: function(options) { + fetch: function (options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var success = options.success; var collection = this; - options.success = function(resp) { + options.success = function (resp) { var method = options.reset ? 'reset' : 'set'; collection[method](resp, options); if (success) success(collection, resp, options); @@ -954,13 +982,13 @@ BI.Factory = { // Create a new instance of a model in this collection. Add the model to the // collection immediately, unless `wait: true` is passed, in which case we // wait for the server to agree. - create: function(model, options) { + create: function (model, options) { options = options ? _.clone(options) : {}; if (!(model = this._prepareModel(model, options))) return false; if (!options.wait) this.add(model, options); var collection = this; var success = options.success; - options.success = function(model, resp) { + options.success = function (model, resp) { if (options.wait) collection.add(model, options); if (success) success(model, resp, options); }; @@ -970,12 +998,12 @@ BI.Factory = { // **parse** converts a response into a list of models to be added to the // collection. The default implementation is just to pass it through. - parse: function(resp, options) { + parse: function (resp, options) { return resp; }, // Create a new collection with an identical list of models as this one. - clone: function() { + clone: function () { return new this.constructor(this.models, { model: this.model, comparator: this.comparator @@ -989,15 +1017,15 @@ BI.Factory = { // Private method to reset all internal state. Called when the collection // is first _initd or reset. - _reset: function() { + _reset: function () { this.length = 0; this.models = []; - this._byId = {}; + this._byId = {}; }, // Prepare a hash of attributes (or other model) to be added to this // collection. - _prepareModel: function(attrs, options) { + _prepareModel: function (attrs, options) { if (this._isModel(attrs)) { if (!attrs.collection) attrs.collection = this; return attrs; @@ -1017,7 +1045,7 @@ BI.Factory = { }, // Internal method to create a model's ties to a collection. - _addReference: function(model, options) { + _addReference: function (model, options) { this._byId[model.cid] = model; var id = this.modelId(model.attributes); if (id != null) this._byId[id] = model; @@ -1025,7 +1053,7 @@ BI.Factory = { }, // Internal method to sever a model's ties to a collection. - _removeReference: function(model, options) { + _removeReference: function (model, options) { if (this === model.collection) delete model.collection; model.off('all', this._onModelEvent, this); }, @@ -1034,7 +1062,7 @@ BI.Factory = { // Sets need to update their indexes when models change ids. All other // events simply proxy through. "add" and "remove" events that originate // in other collections are ignored. - _onModelEvent: function(event, model, collection, options) { + _onModelEvent: function (event, model, collection, options) { if ((event === 'add' || event === 'remove') && collection !== this) return; if (event === 'destroy') this.remove(model, options); if (event === 'change') { @@ -1061,9 +1089,9 @@ BI.Factory = { 'lastIndexOf', 'isEmpty', 'chain', 'sample', 'partition']; // Mix in each Underscore method as a proxy to `Collection#models`. - _.each(methods, function(method) { + _.each(methods, function (method) { if (!_[method]) return; - Collection.prototype[method] = function() { + Collection.prototype[method] = function () { var args = slice.call(arguments); args.unshift(this.models); return _[method].apply(_, args); @@ -1074,10 +1102,10 @@ BI.Factory = { var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy']; // Use attributes instead of properties. - _.each(attributeMethods, function(method) { + _.each(attributeMethods, function (method) { if (!_[method]) return; - Collection.prototype[method] = function(value, context) { - var iterator = _.isFunction(value) ? value : function(model) { + Collection.prototype[method] = function (value, context) { + var iterator = _.isFunction(value) ? value : function (model) { return model.get(value); }; return _[method](this.models, iterator, context); @@ -1097,7 +1125,7 @@ BI.Factory = { // Creating a BI.V creates its initial element outside of the DOM, // if an existing element is not provided... - var V = BI.V = function(options) { + var V = BI.V = function (options) { this.cid = _.uniqueId('view'); options = options || {}; this.options = _.defaults(options, _.result(this, '_defaultConfig')); @@ -1120,28 +1148,33 @@ BI.Factory = { // jQuery delegate for element lookup, scoped to DOM elements within the // current view. This should be preferred to global lookups where possible. - $: function(selector) { + $: function (selector) { return this.$el.find(selector); }, - _defaultConfig: function(){return {}}, + _defaultConfig: function () { + return {} + }, // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + }, //容器,默认放在this.element上 - _vessel: function(){return this}, + _vessel: function () { + return this + }, // **render** is the core function that your view should override, in order // to populate its element (`this.el`), with the appropriate HTML. The // convention is for **render** to always return `this`. - _render: function(vessel) { + render: function (vessel) { return this; }, // Remove this view by taking the element out of the DOM, and removing any // applicable BI.Events listeners. - remove: function() { + remove: function () { this._removeElement(); this.stopListening(); return this; @@ -1150,7 +1183,7 @@ BI.Factory = { // Remove this view's element from the document and all event listeners // attached to it. Exposed for subclasses using an alternative DOM // manipulation API. - _removeElement: function() { + _removeElement: function () { this.$el.remove(); if ($.browser.msie === true) { this.el.outerHTML = ''; @@ -1159,33 +1192,33 @@ BI.Factory = { // Change the view's element (`this.el` property) and re-delegate the // view's events on the new element. - setElement: function(element) { + setElement: function (element) { this.undelegateEvents(); this._setElement(element); - this.$vessel = this._vessel(); - this._render(this.$vessel); + this.vessel = this._vessel(); + this.render(this.vessel); this.delegateEvents(); return this; }, - setVisible: function(visible){ + setVisible: function (visible) { this.options.invisible = !visible; - if (visible){ + if (visible) { this.element.show(); } else { this.element.hide(); } }, - isVisible: function(){ + isVisible: function () { return !this.options.invisible; }, - visible: function(){ + visible: function () { this.setVisible(true); }, - invisible: function(){ + invisible: function () { this.setVisible(false); }, @@ -1194,7 +1227,7 @@ BI.Factory = { // context or an element. Subclasses can override this to utilize an // alternative DOM manipulation API and are only required to set the // `this.el` property. - _setElement: function(el) { + _setElement: function (el) { this.$el = el instanceof BI.$ ? el : BI.$(el); this.element = this.$el; this.el = this.$el[0]; @@ -1213,7 +1246,7 @@ BI.Factory = { // pairs. Callbacks will be bound to the view, with `this` set properly. // Uses event delegation for efficiency. // Omitting the selector binds the event to `this.el`. - delegateEvents: function(events) { + delegateEvents: function (events) { if (!(events || (events = _.result(this, 'events')))) return this; this.undelegateEvents(); for (var key in events) { @@ -1229,27 +1262,27 @@ BI.Factory = { // Add a single event listener to the view's element (or a child element // using `selector`). This only works for delegate-able events: not `focus`, // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer. - delegate: function(eventName, selector, listener) { - this.$vessel.element.on(eventName + '.delegateEvents' + this.cid, selector, listener); + delegate: function (eventName, selector, listener) { + this.vessel.element.on(eventName + '.delegateEvents' + this.cid, selector, listener); }, // Clears all callbacks previously bound to the view by `delegateEvents`. // You usually don't need to use this, but may wish to if you have multiple // BI views attached to the same DOM element. - undelegateEvents: function() { - if (this.$vessel) this.$vessel.element.off('.delegateEvents' + this.cid); + undelegateEvents: function () { + if (this.vessel) this.vessel.element.off('.delegateEvents' + this.cid); return this; }, // A finer-grained `undelegateEvents` for removing a single delegated event. // `selector` and `listener` are both optional. - undelegate: function(eventName, selector, listener) { - this.$vessel.element.off(eventName + '.delegateEvents' + this.cid, selector, listener); + undelegate: function (eventName, selector, listener) { + this.vessel.element.off(eventName + '.delegateEvents' + this.cid, selector, listener); }, // Produces a DOM element to be assigned to your view. Exposed for // subclasses using an alternative DOM manipulation API. - _createElement: function(tagName) { + _createElement: function (tagName) { return document.createElement(tagName); }, @@ -1257,7 +1290,7 @@ BI.Factory = { // If `this.el` is a string, pass it through `$()`, take the first // matching element, and re-assign it to `el`. Otherwise, create // an element from the `id`, `className` and `tagName` properties. - _ensureElement: function() { + _ensureElement: function () { var attrs = _.extend({}, _.result(this, 'attributes')); if (this.baseCls) attrs['class'] = _.result(this, 'baseCls'); if (!this.element) { @@ -1270,7 +1303,7 @@ BI.Factory = { // Set attributes from a hash on this view's element. Exposed for // subclasses using an alternative DOM manipulation API. - _setAttributes: function(attributes) { + _setAttributes: function (attributes) { this.$el.attr(attributes); } @@ -1294,7 +1327,7 @@ BI.Factory = { // instead of `application/json` with the model in a param named `model`. // Useful when interfacing with server-side languages like **PHP** that make // it difficult to read the body of `PUT` requests. - BI.sync = function(method, model, options) { + BI.sync = function (method, model, options) { var type = methodMap[method]; // Default options, unless specified. @@ -1308,8 +1341,8 @@ BI.Factory = { // Ensure that we have a URL. if (!options.url) { - params.url = _.result(model, method+"URL") || _.result(model, 'url'); - if(!params.url){ + params.url = _.result(model, method + "URL") || _.result(model, 'url'); + if (!params.url) { return; } } @@ -1332,7 +1365,7 @@ BI.Factory = { params.type = 'POST'; if (options.emulateJSON) params.data._method = type; var beforeSend = options.beforeSend; - options.beforeSend = function(xhr) { + options.beforeSend = function (xhr) { xhr.setRequestHeader('X-HTTP-Method-Override', type); if (beforeSend) return beforeSend.apply(this, arguments); }; @@ -1345,7 +1378,7 @@ BI.Factory = { // Pass along `textStatus` and `errorThrown` from jQuery. var error = options.error; - options.error = function(xhr, textStatus, errorThrown) { + options.error = function (xhr, textStatus, errorThrown) { options.textStatus = textStatus; options.errorThrown = errorThrown; if (error) error.apply(this, arguments); @@ -1361,9 +1394,9 @@ BI.Factory = { var methodMap = { 'create': 'POST', 'update': 'PUT', - 'patch': 'PATCH', + 'patch': 'PATCH', 'delete': 'DELETE', - 'read': 'GET' + 'read': 'GET' }; // Set the default implementation of `BI.ajax` to proxy through to `$`. @@ -1375,7 +1408,7 @@ BI.Factory = { // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. - var Router = BI.Router = function(options) { + var Router = BI.Router = function (options) { options || (options = {}); if (options.routes) this.routes = options.routes; this._bindRoutes(); @@ -1385,16 +1418,17 @@ BI.Factory = { // Cached regular expressions for matching named param parts and splatted // parts of route strings. var optionalParam = /\((.*?)\)/g; - var namedParam = /(\(\?)?:\w+/g; - var splatParam = /\*\w+/g; - var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; + var namedParam = /(\(\?)?:\w+/g; + var splatParam = /\*\w+/g; + var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; // Set up all inheritable **BI.Router** properties and methods. _.extend(Router.prototype, Events, { // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + }, // Manually bind a single named route to a callback. For example: // @@ -1402,7 +1436,7 @@ BI.Factory = { // ... // }); // - route: function(route, name, callback) { + route: function (route, name, callback) { if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (_.isFunction(name)) { callback = name; @@ -1410,7 +1444,7 @@ BI.Factory = { } if (!callback) callback = this[name]; var router = this; - BI.history.route(route, function(fragment) { + BI.history.route(route, function (fragment) { var args = router._extractParameters(route, fragment); if (router.execute(callback, args, name) !== false) { router.trigger.apply(router, ['route:' + name].concat(args)); @@ -1423,12 +1457,12 @@ BI.Factory = { // Execute a route handler with the provided parameters. This is an // excellent place to do pre-route setup or post-route cleanup. - execute: function(callback, args, name) { + execute: function (callback, args, name) { if (callback) callback.apply(this, args); }, // Simple proxy to `BI.history` to save a fragment into the history. - navigate: function(fragment, options) { + navigate: function (fragment, options) { BI.history.navigate(fragment, options); return this; }, @@ -1436,7 +1470,7 @@ BI.Factory = { // Bind all defined routes to `BI.history`. We have to reverse the // order of the routes here to support behavior where the most general // routes can be defined at the bottom of the route map. - _bindRoutes: function() { + _bindRoutes: function () { if (!this.routes) return; this.routes = _.result(this, 'routes'); var route, routes = _.keys(this.routes); @@ -1447,10 +1481,10 @@ BI.Factory = { // Convert a route string into a regular expression, suitable for matching // against the current location hash. - _routeToRegExp: function(route) { + _routeToRegExp: function (route) { route = route.replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') - .replace(namedParam, function(match, optional) { + .replace(namedParam, function (match, optional) { return optional ? match : '([^/?]+)'; }) .replace(splatParam, '([^?]*?)'); @@ -1460,9 +1494,9 @@ BI.Factory = { // Given a route, and a URL fragment that it matches, return the array of // extracted decoded parameters. Empty or unmatched parameters will be // treated as `null` to normalize cross-browser behavior. - _extractParameters: function(route, fragment) { + _extractParameters: function (route, fragment) { var params = route.exec(fragment).slice(1); - return _.map(params, function(param, i) { + return _.map(params, function (param, i) { // Don't decode the search params. if (i === params.length - 1) return param || null; return param ? decodeURIComponent(param) : null; @@ -1479,7 +1513,7 @@ BI.Factory = { // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) // and URL fragments. If the browser supports neither (old IE, natch), // falls back to polling. - var History = BI.History = function() { + var History = BI.History = function () { this.handlers = []; _.bindAll(this, 'checkUrl'); @@ -1510,27 +1544,27 @@ BI.Factory = { interval: 50, // Are we at the app root? - atRoot: function() { + atRoot: function () { var path = this.location.pathname.replace(/[^\/]$/, '$&/'); return path === this.root && !this.getSearch(); }, // In IE6, the hash fragment and search params are incorrect if the // fragment contains `?`. - getSearch: function() { + getSearch: function () { var match = this.location.href.replace(/#.*/, '').match(/\?.+/); return match ? match[0] : ''; }, // Gets the true hash value. Cannot use location.hash directly due to bug // in Firefox where location.hash will always be decoded. - getHash: function(window) { + getHash: function (window) { var match = (window || this).location.href.match(/#(.*)$/); return match ? match[1] : ''; }, // Get the pathname and search params, without the root. - getPath: function() { + getPath: function () { var path = decodeURI(this.location.pathname + this.getSearch()); var root = this.root.slice(0, -1); if (!path.indexOf(root)) path = path.slice(root.length); @@ -1538,7 +1572,7 @@ BI.Factory = { }, // Get the cross-browser normalized URL fragment from the path or hash. - getFragment: function(fragment) { + getFragment: function (fragment) { if (fragment == null) { if (this._hasPushState || !this._wantsHashChange) { fragment = this.getPath(); @@ -1551,19 +1585,19 @@ BI.Factory = { // Start the hash change handling, returning `true` if the current URL matches // an existing route, and `false` otherwise. - start: function(options) { + start: function (options) { if (History.started) throw new Error('BI.history has already been started'); History.started = true; // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? - this.options = _.extend({root: '/'}, this.options, options); - this.root = this.options.root; + this.options = _.extend({root: '/'}, this.options, options); + this.root = this.options.root; this._wantsHashChange = this.options.hashChange !== false; - this._hasHashChange = 'onhashchange' in window; - this._wantsPushState = !!this.options.pushState; - this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); - this.fragment = this.getFragment(); + this._hasHashChange = 'onhashchange' in window; + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); + this.fragment = this.getFragment(); // Normalize root to always include a leading and trailing slash. this.root = ('/' + this.root + '/').replace(rootStripper, '/'); @@ -1605,8 +1639,8 @@ BI.Factory = { // Add a cross-platform `addEventListener` shim for older browsers. var addEventListener = window.addEventListener || function (eventName, listener) { - return attachEvent('on' + eventName, listener); - }; + return attachEvent('on' + eventName, listener); + }; // Depending on whether we're using pushState or hashes, and whether // 'onhashchange' is supported, determine how we check the URL state. @@ -1623,11 +1657,11 @@ BI.Factory = { // Disable BI.history, perhaps temporarily. Not useful in a real app, // but possibly useful for unit testing Routers. - stop: function() { + stop: function () { // Add a cross-platform `removeEventListener` shim for older browsers. var removeEventListener = window.removeEventListener || function (eventName, listener) { - return detachEvent('on' + eventName, listener); - }; + return detachEvent('on' + eventName, listener); + }; // Remove window listeners. if (this._hasPushState) { @@ -1649,13 +1683,13 @@ BI.Factory = { // Add a route to be tested when the fragment changes. Routes added later // may override previous routes. - route: function(route, callback) { + route: function (route, callback) { this.handlers.unshift({route: route, callback: callback}); }, // Checks the current URL to see if it has changed, and if it has, // calls `loadUrl`, normalizing across the hidden iframe. - checkUrl: function(e) { + checkUrl: function (e) { var current = this.getFragment(); // If the user pressed the back button, the iframe's hash will have @@ -1672,9 +1706,9 @@ BI.Factory = { // Attempt to load the current URL fragment. If a route succeeds with a // match, returns `true`. If no defined routes matches the fragment, // returns `false`. - loadUrl: function(fragment) { + loadUrl: function (fragment) { fragment = this.fragment = this.getFragment(fragment); - return _.any(this.handlers, function(handler) { + return _.any(this.handlers, function (handler) { if (handler.route.test(fragment)) { handler.callback(fragment); return true; @@ -1689,7 +1723,7 @@ BI.Factory = { // The options object can contain `trigger: true` if you wish to have the // route callback be fired (not usually desirable), or `replace: true`, if // you wish to modify the current URL without adding an entry to the history. - navigate: function(fragment, options) { + navigate: function (fragment, options) { if (!History.started) return false; if (!options || options === true) options = {trigger: !!options}; @@ -1735,7 +1769,7 @@ BI.Factory = { // Update the hash location, either replacing the current entry, or adding // a new one to the browser history. - _updateHash: function(location, fragment, replace) { + _updateHash: function (location, fragment, replace) { if (replace) { var href = location.href.replace(/(javascript:|#).*$/, ''); location.replace(href + '#' + fragment); @@ -1756,7 +1790,7 @@ BI.Factory = { // Helper function to correctly set up the prototype chain, for subclasses. // Similar to `goog.inherits`, but uses a hash of prototype properties and // class properties to be extended. - var extend = function(protoProps, staticProps) { + var extend = function (protoProps, staticProps) { var parent = this; var child; @@ -1766,7 +1800,9 @@ BI.Factory = { if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { - child = function(){ return parent.apply(this, arguments); }; + child = function () { + return parent.apply(this, arguments); + }; } // Add static properties to the constructor function, if supplied. @@ -1774,7 +1810,9 @@ BI.Factory = { // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. - var Surrogate = function(){ this.constructor = child; }; + var Surrogate = function () { + this.constructor = child; + }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate; @@ -1793,14 +1831,14 @@ BI.Factory = { M.extend = Collection.extend = Router.extend = V.extend = History.extend = extend; // Throw an error when a URL is needed, and none is supplied. - var urlError = function() { + var urlError = function () { throw new Error('A "url" property or function must be specified'); }; // Wrap an optional error callback with a fallback error event. - var wrapError = function(model, options) { + var wrapError = function (model, options) { var error = options.error; - options.error = function(resp) { + options.error = function (resp) { if (error) error(model, resp, options); model.trigger('error', model, resp, options); }; diff --git a/dist/core.js b/dist/core.js index 19d0eea11..827b2c13e 100644 --- a/dist/core.js +++ b/dist/core.js @@ -11065,9 +11065,9 @@ BI.Factory = { // } return view; } -};(function(root, factory) { - root.BI = factory(root, root.BI || {}, root._, (root.jQuery || root.$)); -}(this, function(root, BI, _, $) { +};(function (root, factory) { + root.BI = factory(root, root.BI || {}, root._, (root.jQuery || root.$)); +}(this, function (root, BI, _, $) { var previousBI = root.BI; @@ -11084,7 +11084,7 @@ BI.Factory = { // Runs BI.js in *noConflict* mode, returning the `BI` variable // to its previous owner. Returns a reference to this BI object. - BI.noConflict = function() { + BI.noConflict = function () { root.BI = previousBI; return this; }; @@ -11117,7 +11117,7 @@ BI.Factory = { // Bind an event to a `callback` function. Passing `"all"` will bind // the callback to all events fired. - on: function(name, callback, context) { + 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] = []); @@ -11127,10 +11127,10 @@ BI.Factory = { // 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) { + once: function (name, callback, context) { if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; var self = this; - var once = _.once(function() { + var once = _.once(function () { self.off(name, once); callback.apply(this, arguments); }); @@ -11142,7 +11142,7 @@ BI.Factory = { // 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) { + off: function (name, callback, context) { if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; // Remove all callbacks for all events. @@ -11173,7 +11173,7 @@ BI.Factory = { callback && callback !== event.callback && callback !== event.callback._callback || context && context !== event.context - ) { + ) { remaining.push(event); } } @@ -11189,11 +11189,15 @@ BI.Factory = { return this; }, + un: function () { + this.off.apply(this, arguments); + }, + // 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) { + trigger: function (name) { if (!this._events) return this; var args = slice.call(arguments, 1); if (!eventsApi(this, 'trigger', name, args)) return this; @@ -11204,10 +11208,14 @@ BI.Factory = { return this; }, + fireEvent: function () { + this.trigger.apply(this, arguments); + }, + // 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. - listenTo: function(obj, name, callback) { + listenTo: function (obj, name, callback) { var listeningTo = this._listeningTo || (this._listeningTo = {}); var id = obj._listenId || (obj._listenId = _.uniqueId('l')); listeningTo[id] = obj; @@ -11216,7 +11224,7 @@ BI.Factory = { return this; }, - listenToOnce: function(obj, name, callback) { + listenToOnce: function (obj, name, callback) { if (typeof name === 'object') { for (var event in name) this.listenToOnce(obj, event, name[event]); return this; @@ -11229,7 +11237,7 @@ BI.Factory = { return this; } if (!callback) return this; - var once = _.once(function() { + var once = _.once(function () { this.stopListening(obj, name, once); callback.apply(this, arguments); }); @@ -11239,7 +11247,7 @@ BI.Factory = { // Tell this object to stop listening to either specific events ... or // to every object it's currently listening to. - stopListening: function(obj, name, callback) { + stopListening: function (obj, name, callback) { var listeningTo = this._listeningTo; if (!listeningTo) return this; var remove = !name && !callback; @@ -11261,7 +11269,7 @@ BI.Factory = { // 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) { + var eventsApi = function (obj, action, name, rest) { if (!name) return true; // Handle event maps. @@ -11287,19 +11295,29 @@ BI.Factory = { // A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // BI events have 3 arguments). - var triggerEvents = function(events, args) { + 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); return; + 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); + return; } }; // Aliases for backwards compatibility. - Events.bind = Events.on; + Events.bind = Events.on; Events.unbind = Events.off; // Allow the `BI` object to serve as a global event bus, for folks who @@ -11316,7 +11334,7 @@ BI.Factory = { // Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. - var M = BI.M = function(attributes, options) { + var M = BI.M = function (attributes, options) { var attrs = attributes || {}; options = options || {}; this.cid = _.uniqueId('c'); @@ -11345,40 +11363,47 @@ BI.Factory = { // CouchDB users may want to set this to `"_id"`. idAttribute: 'ID', - _defaultConfig: function(){return {}}, + _defaultConfig: function () { + return {} + }, + + init: function () { + }, // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + this.init(); + }, // Return a copy of the model's `attributes` object. - toJSON: function(options) { + toJSON: function (options) { return _.clone(this.attributes); }, // Proxy `BI.sync` by default -- but override this if you need // custom syncing semantics for *this* particular model. - sync: function() { + sync: function () { return BI.sync.apply(this, arguments); }, // Get the value of an attribute. - get: function(attr) { + get: function (attr) { return this.attributes[attr]; }, // Get the HTML-escaped value of an attribute. - escape: function(attr) { + 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) { + has: function (attr) { return _.has(this.attributes, attr); }, // Special-cased proxy to underscore's `_.matches` method. - matches: function(attrs) { + matches: function (attrs) { var keys = _.keys(attrs), length = keys.length; var obj = Object(this.attributes); for (var i = 0; i < length; i++) { @@ -11391,7 +11416,7 @@ BI.Factory = { // 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) { + set: function (key, val, options) { var attr, attrs, unset, changes, silent, changing, changed, prev, current; if (key == null) return this; @@ -11409,11 +11434,11 @@ BI.Factory = { if (!this._validate(attrs, options)) return false; // Extract attributes and options. - unset = options.unset; - silent = options.silent; - changes = []; - changing = this._changing; - this._changing = true; + unset = options.unset; + silent = options.silent; + changes = []; + changing = this._changing; + this._changing = true; if (!changing) { this._previousAttributes = _.clone(this.attributes); @@ -11463,12 +11488,12 @@ BI.Factory = { // Remove an attribute from the model, firing `"change"`. `unset` is a noop // if the attribute doesn't exist. - unset: function(attr, options) { + unset: function (attr, options) { return this.set(attr, void 0, _.extend({}, options, {unset: true})); }, // Clear all attributes on the model, firing `"change"`. - clear: function(options) { + clear: function (options) { var attrs = {}; for (var key in this.attributes) attrs[key] = void 0; return this.set(attrs, _.extend({}, options, {unset: true})); @@ -11476,7 +11501,7 @@ BI.Factory = { // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. - hasChanged: function(attr) { + hasChanged: function (attr) { if (attr == null) return !_.isEmpty(this.changed); return _.has(this.changed, attr); }, @@ -11487,7 +11512,7 @@ BI.Factory = { // persisted to the server. Unset attributes will be set to undefined. // You can also pass an attributes object to diff against the model, // determining if there *would be* a change. - changedAttributes: function(diff) { + changedAttributes: function (diff) { if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; var val, changed = false; var old = this._changing ? this._previousAttributes : this.attributes; @@ -11500,27 +11525,27 @@ BI.Factory = { // Get the previous value of an attribute, recorded at the time the last // `"change"` event was fired. - previous: function(attr) { + previous: function (attr) { if (attr == null || !this._previousAttributes) return null; return this._previousAttributes[attr]; }, // Get all of the attributes of the model at the time of the previous // `"change"` event. - previousAttributes: function() { + previousAttributes: function () { return _.clone(this._previousAttributes); }, // Fetch the model from the server. If the server's representation of the // model differs from its current attributes, they will be overridden, // triggering a `"change"` event. - fetch: function(options) { + fetch: function (options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; - options.success = function(resp) { - if(!options.noset) { + options.success = function (resp) { + if (!options.noset) { if (!model.set(model.parse(resp, options), options)) return false; } if (success) success(resp, model, options); @@ -11533,7 +11558,7 @@ BI.Factory = { // Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. - save: function(key, val, options) { + save: function (key, val, options) { var attrs, method, xhr, attributes = this.attributes; // Handle both `"key", value` and `{key: value}` -style arguments. @@ -11565,7 +11590,7 @@ BI.Factory = { if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; - options.success = function(resp) { + options.success = function (resp) { // Ensure attributes are restored during synchronous saves. model.attributes = attributes; var serverAttrs = model.parse(resp, options); @@ -11592,17 +11617,17 @@ BI.Factory = { // Destroy this model on the server if it was already persisted. // Optimistically removes the model from its collection, if it has one. // If `wait: true` is passed, waits for the server to respond before removal. - destroy: function(options) { + destroy: function (options) { options = options ? _.clone(options) : {}; var model = this; var success = options.success; - var destroy = function() { + var destroy = function () { model.stopListening(); model.trigger('destroy', model.collection, model, options); }; - options.success = function(resp) { + options.success = function (resp) { if (options.wait || model.isNew()) destroy(); if (success) success(resp, model, options); if (!model.isNew()) model.trigger('sync', resp, model, options).trigger('delete', resp, model, options); @@ -11622,7 +11647,7 @@ BI.Factory = { // Default URL for the model's representation on the server -- if you're // using BI's restful methods, override this to change the endpoint // that will be called. - url: function() { + url: function () { var base = _.result(this.collection, 'url'); if (this.isNew()) return base; @@ -11631,28 +11656,28 @@ BI.Factory = { // **parse** converts a response into the hash of attributes to be `set` on // the model. The default implementation is just to pass the response along. - parse: function(resp, options) { + parse: function (resp, options) { return resp; }, // Create a new model with identical attributes to this one. - clone: function() { + clone: function () { return new this.constructor(this.attributes); }, // A model is new if it has never been saved to the server, and lacks an id. - isNew: function() { + isNew: function () { return !this.has(this.idAttribute); }, // Check if the model is currently in a valid state. - isValid: function(options) { - return this._validate({}, _.extend(options || {}, { validate: true })); + isValid: function (options) { + return this._validate({}, _.extend(options || {}, {validate: true})); }, // Run validation against the next complete set of model attributes, // returning `true` if all is well. Otherwise, fire an `"invalid"` event. - _validate: function(attrs, options) { + _validate: function (attrs, options) { if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); var error = this.validationError = this.validate(attrs, options) || null; @@ -11667,9 +11692,9 @@ BI.Factory = { var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit', 'chain', 'isEmpty']; // Mix in each Underscore method as a proxy to `M#attributes`. - _.each(modelMethods, function(method) { + _.each(modelMethods, function (method) { if (!_[method]) return; - M.prototype[method] = function() { + M.prototype[method] = function () { var args = slice.call(arguments); args.unshift(this.attributes); return _[method].apply(_, args); @@ -11689,7 +11714,7 @@ BI.Factory = { // Create a new **Collection**, perhaps to contain a specific type of `model`. // If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. - var Collection = BI.Collection = function(models, options) { + var Collection = BI.Collection = function (models, options) { this.options = options = options || {}; if (options.model) this.model = options.model; if (options.comparator !== void 0) this.comparator = options.comparator; @@ -11711,26 +11736,29 @@ BI.Factory = { // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + }, // The JSON representation of a Collection is an array of the // models' attributes. - toJSON: function(options) { - return this.map(function(model){ return model.toJSON(options); }); + toJSON: function (options) { + return this.map(function (model) { + return model.toJSON(options); + }); }, // Proxy `BI.sync` by default. - sync: function() { + sync: function () { return BI.sync.apply(this, arguments); }, // Add a model, or list of models to the set. - add: function(models, options) { + add: function (models, options) { return this.set(models, _.extend({merge: false}, options, addOptions)); }, // Remove a model, or a list of models from the set. - remove: function(models, options) { + remove: function (models, options) { var singular = !_.isArray(models); models = singular ? [models] : _.clone(models); options || (options = {}); @@ -11756,7 +11784,7 @@ BI.Factory = { // removing models that are no longer present, and merging models that // already exist in the collection, as necessary. Similar to **M#set**, // the core operation for updating the data contained by the collection. - set: function(models, options) { + set: function (models, options) { options = _.defaults({}, options, setOptions); if (options.parse) models = this.parse(models, options); var singular = !_.isArray(models); @@ -11857,7 +11885,7 @@ BI.Factory = { // you can reset the entire set with a new list of models, without firing // any granular `add` or `remove` events. Fires `reset` when finished. // Useful for bulk operations and optimizations. - reset: function(models, options) { + reset: function (models, options) { options = options ? _.clone(options) : {}; for (var i = 0, length = this.models.length; i < length; i++) { this._removeReference(this.models[i], options); @@ -11870,66 +11898,66 @@ BI.Factory = { }, // Add a model to the end of the collection. - push: function(model, options) { + push: function (model, options) { return this.add(model, _.extend({at: this.length}, options)); }, // Remove a model from the end of the collection. - pop: function(options) { + pop: function (options) { var model = this.at(this.length - 1); this.remove(model, options); return model; }, // Add a model to the beginning of the collection. - unshift: function(model, options) { + unshift: function (model, options) { return this.add(model, _.extend({at: 0}, options)); }, // Remove a model from the beginning of the collection. - shift: function(options) { + shift: function (options) { var model = this.at(0); this.remove(model, options); return model; }, // Slice out a sub-array of models from the collection. - slice: function() { + slice: function () { return slice.apply(this.models, arguments); }, // Get a model from the set by id. - get: function(obj) { + get: function (obj) { if (obj == null) return void 0; var id = this.modelId(this._isModel(obj) ? obj.attributes : obj); return this._byId[obj] || this._byId[id] || this._byId[obj.cid]; }, // Get the model at the given index. - at: function(index) { + at: function (index) { if (index < 0) index += this.length; return this.models[index]; }, // Return models with matching attributes. Useful for simple cases of // `filter`. - where: function(attrs, first) { + where: function (attrs, first) { var matches = _.matches(attrs); - return this[first ? 'find' : 'filter'](function(model) { + return this[first ? 'find' : 'filter'](function (model) { return matches(model.attributes); }); }, // Return the first model with matching attributes. Useful for simple cases // of `find`. - findWhere: function(attrs) { + findWhere: function (attrs) { return this.where(attrs, true); }, // Force the collection to re-sort itself. You don't need to call this under // normal circumstances, as the set will maintain sort order as each item // is added. - sort: function(options) { + sort: function (options) { if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); options || (options = {}); @@ -11945,19 +11973,19 @@ BI.Factory = { }, // Pluck an attribute from each model in the collection. - pluck: function(attr) { + pluck: function (attr) { return _.invoke(this.models, 'get', attr); }, // Fetch the default set of models for this collection, resetting the // collection when they arrive. If `reset: true` is passed, the response // data will be passed through the `reset` method instead of `set`. - fetch: function(options) { + fetch: function (options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var success = options.success; var collection = this; - options.success = function(resp) { + options.success = function (resp) { var method = options.reset ? 'reset' : 'set'; collection[method](resp, options); if (success) success(collection, resp, options); @@ -11970,13 +11998,13 @@ BI.Factory = { // Create a new instance of a model in this collection. Add the model to the // collection immediately, unless `wait: true` is passed, in which case we // wait for the server to agree. - create: function(model, options) { + create: function (model, options) { options = options ? _.clone(options) : {}; if (!(model = this._prepareModel(model, options))) return false; if (!options.wait) this.add(model, options); var collection = this; var success = options.success; - options.success = function(model, resp) { + options.success = function (model, resp) { if (options.wait) collection.add(model, options); if (success) success(model, resp, options); }; @@ -11986,12 +12014,12 @@ BI.Factory = { // **parse** converts a response into a list of models to be added to the // collection. The default implementation is just to pass it through. - parse: function(resp, options) { + parse: function (resp, options) { return resp; }, // Create a new collection with an identical list of models as this one. - clone: function() { + clone: function () { return new this.constructor(this.models, { model: this.model, comparator: this.comparator @@ -12005,15 +12033,15 @@ BI.Factory = { // Private method to reset all internal state. Called when the collection // is first _initd or reset. - _reset: function() { + _reset: function () { this.length = 0; this.models = []; - this._byId = {}; + this._byId = {}; }, // Prepare a hash of attributes (or other model) to be added to this // collection. - _prepareModel: function(attrs, options) { + _prepareModel: function (attrs, options) { if (this._isModel(attrs)) { if (!attrs.collection) attrs.collection = this; return attrs; @@ -12033,7 +12061,7 @@ BI.Factory = { }, // Internal method to create a model's ties to a collection. - _addReference: function(model, options) { + _addReference: function (model, options) { this._byId[model.cid] = model; var id = this.modelId(model.attributes); if (id != null) this._byId[id] = model; @@ -12041,7 +12069,7 @@ BI.Factory = { }, // Internal method to sever a model's ties to a collection. - _removeReference: function(model, options) { + _removeReference: function (model, options) { if (this === model.collection) delete model.collection; model.off('all', this._onModelEvent, this); }, @@ -12050,7 +12078,7 @@ BI.Factory = { // Sets need to update their indexes when models change ids. All other // events simply proxy through. "add" and "remove" events that originate // in other collections are ignored. - _onModelEvent: function(event, model, collection, options) { + _onModelEvent: function (event, model, collection, options) { if ((event === 'add' || event === 'remove') && collection !== this) return; if (event === 'destroy') this.remove(model, options); if (event === 'change') { @@ -12077,9 +12105,9 @@ BI.Factory = { 'lastIndexOf', 'isEmpty', 'chain', 'sample', 'partition']; // Mix in each Underscore method as a proxy to `Collection#models`. - _.each(methods, function(method) { + _.each(methods, function (method) { if (!_[method]) return; - Collection.prototype[method] = function() { + Collection.prototype[method] = function () { var args = slice.call(arguments); args.unshift(this.models); return _[method].apply(_, args); @@ -12090,10 +12118,10 @@ BI.Factory = { var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy']; // Use attributes instead of properties. - _.each(attributeMethods, function(method) { + _.each(attributeMethods, function (method) { if (!_[method]) return; - Collection.prototype[method] = function(value, context) { - var iterator = _.isFunction(value) ? value : function(model) { + Collection.prototype[method] = function (value, context) { + var iterator = _.isFunction(value) ? value : function (model) { return model.get(value); }; return _[method](this.models, iterator, context); @@ -12113,7 +12141,7 @@ BI.Factory = { // Creating a BI.V creates its initial element outside of the DOM, // if an existing element is not provided... - var V = BI.V = function(options) { + var V = BI.V = function (options) { this.cid = _.uniqueId('view'); options = options || {}; this.options = _.defaults(options, _.result(this, '_defaultConfig')); @@ -12136,28 +12164,33 @@ BI.Factory = { // jQuery delegate for element lookup, scoped to DOM elements within the // current view. This should be preferred to global lookups where possible. - $: function(selector) { + $: function (selector) { return this.$el.find(selector); }, - _defaultConfig: function(){return {}}, + _defaultConfig: function () { + return {} + }, // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + }, //容器,默认放在this.element上 - _vessel: function(){return this}, + _vessel: function () { + return this + }, // **render** is the core function that your view should override, in order // to populate its element (`this.el`), with the appropriate HTML. The // convention is for **render** to always return `this`. - _render: function(vessel) { + render: function (vessel) { return this; }, // Remove this view by taking the element out of the DOM, and removing any // applicable BI.Events listeners. - remove: function() { + remove: function () { this._removeElement(); this.stopListening(); return this; @@ -12166,7 +12199,7 @@ BI.Factory = { // Remove this view's element from the document and all event listeners // attached to it. Exposed for subclasses using an alternative DOM // manipulation API. - _removeElement: function() { + _removeElement: function () { this.$el.remove(); if ($.browser.msie === true) { this.el.outerHTML = ''; @@ -12175,33 +12208,33 @@ BI.Factory = { // Change the view's element (`this.el` property) and re-delegate the // view's events on the new element. - setElement: function(element) { + setElement: function (element) { this.undelegateEvents(); this._setElement(element); - this.$vessel = this._vessel(); - this._render(this.$vessel); + this.vessel = this._vessel(); + this.render(this.vessel); this.delegateEvents(); return this; }, - setVisible: function(visible){ + setVisible: function (visible) { this.options.invisible = !visible; - if (visible){ + if (visible) { this.element.show(); } else { this.element.hide(); } }, - isVisible: function(){ + isVisible: function () { return !this.options.invisible; }, - visible: function(){ + visible: function () { this.setVisible(true); }, - invisible: function(){ + invisible: function () { this.setVisible(false); }, @@ -12210,7 +12243,7 @@ BI.Factory = { // context or an element. Subclasses can override this to utilize an // alternative DOM manipulation API and are only required to set the // `this.el` property. - _setElement: function(el) { + _setElement: function (el) { this.$el = el instanceof BI.$ ? el : BI.$(el); this.element = this.$el; this.el = this.$el[0]; @@ -12229,7 +12262,7 @@ BI.Factory = { // pairs. Callbacks will be bound to the view, with `this` set properly. // Uses event delegation for efficiency. // Omitting the selector binds the event to `this.el`. - delegateEvents: function(events) { + delegateEvents: function (events) { if (!(events || (events = _.result(this, 'events')))) return this; this.undelegateEvents(); for (var key in events) { @@ -12245,27 +12278,27 @@ BI.Factory = { // Add a single event listener to the view's element (or a child element // using `selector`). This only works for delegate-able events: not `focus`, // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer. - delegate: function(eventName, selector, listener) { - this.$vessel.element.on(eventName + '.delegateEvents' + this.cid, selector, listener); + delegate: function (eventName, selector, listener) { + this.vessel.element.on(eventName + '.delegateEvents' + this.cid, selector, listener); }, // Clears all callbacks previously bound to the view by `delegateEvents`. // You usually don't need to use this, but may wish to if you have multiple // BI views attached to the same DOM element. - undelegateEvents: function() { - if (this.$vessel) this.$vessel.element.off('.delegateEvents' + this.cid); + undelegateEvents: function () { + if (this.vessel) this.vessel.element.off('.delegateEvents' + this.cid); return this; }, // A finer-grained `undelegateEvents` for removing a single delegated event. // `selector` and `listener` are both optional. - undelegate: function(eventName, selector, listener) { - this.$vessel.element.off(eventName + '.delegateEvents' + this.cid, selector, listener); + undelegate: function (eventName, selector, listener) { + this.vessel.element.off(eventName + '.delegateEvents' + this.cid, selector, listener); }, // Produces a DOM element to be assigned to your view. Exposed for // subclasses using an alternative DOM manipulation API. - _createElement: function(tagName) { + _createElement: function (tagName) { return document.createElement(tagName); }, @@ -12273,7 +12306,7 @@ BI.Factory = { // If `this.el` is a string, pass it through `$()`, take the first // matching element, and re-assign it to `el`. Otherwise, create // an element from the `id`, `className` and `tagName` properties. - _ensureElement: function() { + _ensureElement: function () { var attrs = _.extend({}, _.result(this, 'attributes')); if (this.baseCls) attrs['class'] = _.result(this, 'baseCls'); if (!this.element) { @@ -12286,7 +12319,7 @@ BI.Factory = { // Set attributes from a hash on this view's element. Exposed for // subclasses using an alternative DOM manipulation API. - _setAttributes: function(attributes) { + _setAttributes: function (attributes) { this.$el.attr(attributes); } @@ -12310,7 +12343,7 @@ BI.Factory = { // instead of `application/json` with the model in a param named `model`. // Useful when interfacing with server-side languages like **PHP** that make // it difficult to read the body of `PUT` requests. - BI.sync = function(method, model, options) { + BI.sync = function (method, model, options) { var type = methodMap[method]; // Default options, unless specified. @@ -12324,8 +12357,8 @@ BI.Factory = { // Ensure that we have a URL. if (!options.url) { - params.url = _.result(model, method+"URL") || _.result(model, 'url'); - if(!params.url){ + params.url = _.result(model, method + "URL") || _.result(model, 'url'); + if (!params.url) { return; } } @@ -12348,7 +12381,7 @@ BI.Factory = { params.type = 'POST'; if (options.emulateJSON) params.data._method = type; var beforeSend = options.beforeSend; - options.beforeSend = function(xhr) { + options.beforeSend = function (xhr) { xhr.setRequestHeader('X-HTTP-Method-Override', type); if (beforeSend) return beforeSend.apply(this, arguments); }; @@ -12361,7 +12394,7 @@ BI.Factory = { // Pass along `textStatus` and `errorThrown` from jQuery. var error = options.error; - options.error = function(xhr, textStatus, errorThrown) { + options.error = function (xhr, textStatus, errorThrown) { options.textStatus = textStatus; options.errorThrown = errorThrown; if (error) error.apply(this, arguments); @@ -12377,9 +12410,9 @@ BI.Factory = { var methodMap = { 'create': 'POST', 'update': 'PUT', - 'patch': 'PATCH', + 'patch': 'PATCH', 'delete': 'DELETE', - 'read': 'GET' + 'read': 'GET' }; // Set the default implementation of `BI.ajax` to proxy through to `$`. @@ -12391,7 +12424,7 @@ BI.Factory = { // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. - var Router = BI.Router = function(options) { + var Router = BI.Router = function (options) { options || (options = {}); if (options.routes) this.routes = options.routes; this._bindRoutes(); @@ -12401,16 +12434,17 @@ BI.Factory = { // Cached regular expressions for matching named param parts and splatted // parts of route strings. var optionalParam = /\((.*?)\)/g; - var namedParam = /(\(\?)?:\w+/g; - var splatParam = /\*\w+/g; - var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; + var namedParam = /(\(\?)?:\w+/g; + var splatParam = /\*\w+/g; + var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; // Set up all inheritable **BI.Router** properties and methods. _.extend(Router.prototype, Events, { // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + }, // Manually bind a single named route to a callback. For example: // @@ -12418,7 +12452,7 @@ BI.Factory = { // ... // }); // - route: function(route, name, callback) { + route: function (route, name, callback) { if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (_.isFunction(name)) { callback = name; @@ -12426,7 +12460,7 @@ BI.Factory = { } if (!callback) callback = this[name]; var router = this; - BI.history.route(route, function(fragment) { + BI.history.route(route, function (fragment) { var args = router._extractParameters(route, fragment); if (router.execute(callback, args, name) !== false) { router.trigger.apply(router, ['route:' + name].concat(args)); @@ -12439,12 +12473,12 @@ BI.Factory = { // Execute a route handler with the provided parameters. This is an // excellent place to do pre-route setup or post-route cleanup. - execute: function(callback, args, name) { + execute: function (callback, args, name) { if (callback) callback.apply(this, args); }, // Simple proxy to `BI.history` to save a fragment into the history. - navigate: function(fragment, options) { + navigate: function (fragment, options) { BI.history.navigate(fragment, options); return this; }, @@ -12452,7 +12486,7 @@ BI.Factory = { // Bind all defined routes to `BI.history`. We have to reverse the // order of the routes here to support behavior where the most general // routes can be defined at the bottom of the route map. - _bindRoutes: function() { + _bindRoutes: function () { if (!this.routes) return; this.routes = _.result(this, 'routes'); var route, routes = _.keys(this.routes); @@ -12463,10 +12497,10 @@ BI.Factory = { // Convert a route string into a regular expression, suitable for matching // against the current location hash. - _routeToRegExp: function(route) { + _routeToRegExp: function (route) { route = route.replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') - .replace(namedParam, function(match, optional) { + .replace(namedParam, function (match, optional) { return optional ? match : '([^/?]+)'; }) .replace(splatParam, '([^?]*?)'); @@ -12476,9 +12510,9 @@ BI.Factory = { // Given a route, and a URL fragment that it matches, return the array of // extracted decoded parameters. Empty or unmatched parameters will be // treated as `null` to normalize cross-browser behavior. - _extractParameters: function(route, fragment) { + _extractParameters: function (route, fragment) { var params = route.exec(fragment).slice(1); - return _.map(params, function(param, i) { + return _.map(params, function (param, i) { // Don't decode the search params. if (i === params.length - 1) return param || null; return param ? decodeURIComponent(param) : null; @@ -12495,7 +12529,7 @@ BI.Factory = { // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) // and URL fragments. If the browser supports neither (old IE, natch), // falls back to polling. - var History = BI.History = function() { + var History = BI.History = function () { this.handlers = []; _.bindAll(this, 'checkUrl'); @@ -12526,27 +12560,27 @@ BI.Factory = { interval: 50, // Are we at the app root? - atRoot: function() { + atRoot: function () { var path = this.location.pathname.replace(/[^\/]$/, '$&/'); return path === this.root && !this.getSearch(); }, // In IE6, the hash fragment and search params are incorrect if the // fragment contains `?`. - getSearch: function() { + getSearch: function () { var match = this.location.href.replace(/#.*/, '').match(/\?.+/); return match ? match[0] : ''; }, // Gets the true hash value. Cannot use location.hash directly due to bug // in Firefox where location.hash will always be decoded. - getHash: function(window) { + getHash: function (window) { var match = (window || this).location.href.match(/#(.*)$/); return match ? match[1] : ''; }, // Get the pathname and search params, without the root. - getPath: function() { + getPath: function () { var path = decodeURI(this.location.pathname + this.getSearch()); var root = this.root.slice(0, -1); if (!path.indexOf(root)) path = path.slice(root.length); @@ -12554,7 +12588,7 @@ BI.Factory = { }, // Get the cross-browser normalized URL fragment from the path or hash. - getFragment: function(fragment) { + getFragment: function (fragment) { if (fragment == null) { if (this._hasPushState || !this._wantsHashChange) { fragment = this.getPath(); @@ -12567,19 +12601,19 @@ BI.Factory = { // Start the hash change handling, returning `true` if the current URL matches // an existing route, and `false` otherwise. - start: function(options) { + start: function (options) { if (History.started) throw new Error('BI.history has already been started'); History.started = true; // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? - this.options = _.extend({root: '/'}, this.options, options); - this.root = this.options.root; + this.options = _.extend({root: '/'}, this.options, options); + this.root = this.options.root; this._wantsHashChange = this.options.hashChange !== false; - this._hasHashChange = 'onhashchange' in window; - this._wantsPushState = !!this.options.pushState; - this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); - this.fragment = this.getFragment(); + this._hasHashChange = 'onhashchange' in window; + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); + this.fragment = this.getFragment(); // Normalize root to always include a leading and trailing slash. this.root = ('/' + this.root + '/').replace(rootStripper, '/'); @@ -12621,8 +12655,8 @@ BI.Factory = { // Add a cross-platform `addEventListener` shim for older browsers. var addEventListener = window.addEventListener || function (eventName, listener) { - return attachEvent('on' + eventName, listener); - }; + return attachEvent('on' + eventName, listener); + }; // Depending on whether we're using pushState or hashes, and whether // 'onhashchange' is supported, determine how we check the URL state. @@ -12639,11 +12673,11 @@ BI.Factory = { // Disable BI.history, perhaps temporarily. Not useful in a real app, // but possibly useful for unit testing Routers. - stop: function() { + stop: function () { // Add a cross-platform `removeEventListener` shim for older browsers. var removeEventListener = window.removeEventListener || function (eventName, listener) { - return detachEvent('on' + eventName, listener); - }; + return detachEvent('on' + eventName, listener); + }; // Remove window listeners. if (this._hasPushState) { @@ -12665,13 +12699,13 @@ BI.Factory = { // Add a route to be tested when the fragment changes. Routes added later // may override previous routes. - route: function(route, callback) { + route: function (route, callback) { this.handlers.unshift({route: route, callback: callback}); }, // Checks the current URL to see if it has changed, and if it has, // calls `loadUrl`, normalizing across the hidden iframe. - checkUrl: function(e) { + checkUrl: function (e) { var current = this.getFragment(); // If the user pressed the back button, the iframe's hash will have @@ -12688,9 +12722,9 @@ BI.Factory = { // Attempt to load the current URL fragment. If a route succeeds with a // match, returns `true`. If no defined routes matches the fragment, // returns `false`. - loadUrl: function(fragment) { + loadUrl: function (fragment) { fragment = this.fragment = this.getFragment(fragment); - return _.any(this.handlers, function(handler) { + return _.any(this.handlers, function (handler) { if (handler.route.test(fragment)) { handler.callback(fragment); return true; @@ -12705,7 +12739,7 @@ BI.Factory = { // The options object can contain `trigger: true` if you wish to have the // route callback be fired (not usually desirable), or `replace: true`, if // you wish to modify the current URL without adding an entry to the history. - navigate: function(fragment, options) { + navigate: function (fragment, options) { if (!History.started) return false; if (!options || options === true) options = {trigger: !!options}; @@ -12751,7 +12785,7 @@ BI.Factory = { // Update the hash location, either replacing the current entry, or adding // a new one to the browser history. - _updateHash: function(location, fragment, replace) { + _updateHash: function (location, fragment, replace) { if (replace) { var href = location.href.replace(/(javascript:|#).*$/, ''); location.replace(href + '#' + fragment); @@ -12772,7 +12806,7 @@ BI.Factory = { // Helper function to correctly set up the prototype chain, for subclasses. // Similar to `goog.inherits`, but uses a hash of prototype properties and // class properties to be extended. - var extend = function(protoProps, staticProps) { + var extend = function (protoProps, staticProps) { var parent = this; var child; @@ -12782,7 +12816,9 @@ BI.Factory = { if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { - child = function(){ return parent.apply(this, arguments); }; + child = function () { + return parent.apply(this, arguments); + }; } // Add static properties to the constructor function, if supplied. @@ -12790,7 +12826,9 @@ BI.Factory = { // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. - var Surrogate = function(){ this.constructor = child; }; + var Surrogate = function () { + this.constructor = child; + }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate; @@ -12809,14 +12847,14 @@ BI.Factory = { M.extend = Collection.extend = Router.extend = V.extend = History.extend = extend; // Throw an error when a URL is needed, and none is supplied. - var urlError = function() { + var urlError = function () { throw new Error('A "url" property or function must be specified'); }; // Wrap an optional error callback with a fallback error event. - var wrapError = function(model, options) { + var wrapError = function (model, options) { var error = options.error; - options.error = function(resp) { + options.error = function (resp) { if (error) error(model, resp, options); model.trigger('error', model, resp, options); }; @@ -23188,7 +23226,184 @@ BI.HorizontalFillLayoutLogic = BI.inherit(BI.Logic, { _init: function () { BI.HorizontalFillLayoutLogic.superclass._init.apply(this, arguments); } -});BI.Plugin = BI.Plugin || {}; +});// BI.M = BI.inherit(BI.OB, { +// validationError: null, +// +// _init: function () { +// BI.M.superclass._init.apply(this, arguments); +// this.attributes = {}; +// _.extend(this, _.pick(this.options, ['rootURL', 'parent', 'data', 'id'])); +// this.set(this.options); +// this.changed = {}; +// }, +// +// _validate: function (attrs, options) { +// if (!options.validate || !this.validate) return true; +// attrs = _.extend({}, this.attributes, attrs); +// var error = this.validationError = this.validate(attrs, options) || null; +// if (!error) return true; +// this.fireEvent('invalid', error, this, _.extend(options, {validationError: error})); +// return false; +// }, +// +// toJSON: function (options) { +// return _.clone(this.attributes); +// }, +// +// get: function (attr) { +// return this.attributes[attr]; +// }, +// +// has: function (attr) { +// return _.has(this.attributes, attr); +// }, +// +// matches: function (attrs) { +// var keys = _.keys(attrs), length = keys.length; +// var obj = Object(this.attributes); +// for (var i = 0; i < length; i++) { +// var key = keys[i]; +// if (!_.isEqual(attrs[key], obj[key]) || !(key in obj)) return false; +// } +// return true; +// }, +// +// set: function (key, val, options) { +// var attr, attrs, unset, changes, silent, changing, changed, 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 = options; +// for (var i = 0, length = changes.length; i < length; 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; +// changed = BI.clone(this.changed); +// if (!silent) { +// while (this._pending) { +// options = this._pending; +// this._pending = false; +// this.trigger('change', changed, prev, this, options); +// } +// } +// this._pending = false; +// this._changing = false; +// if (!silent && changes.length) this.trigger("changed", changed, prev, this, options); +// return this; +// }, +// +// unset: function (attr, options) { +// return this.set(attr, void 0, _.extend({}, options, {unset: true})); +// }, +// +// clear: function (options) { +// var attrs = {}; +// for (var key in this.attributes) attrs[key] = void 0; +// return this.set(attrs, _.extend({}, options, {unset: true})); +// }, +// +// hasChanged: function (attr) { +// if (attr == null) return !_.isEmpty(this.changed); +// return _.has(this.changed, attr); +// }, +// +// changedAttributes: function (diff) { +// if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; +// var val, changed = false; +// var old = this._changing ? this._previousAttributes : this.attributes; +// for (var attr in diff) { +// if (_.isEqual(old[attr], (val = diff[attr]))) continue; +// (changed || (changed = {}))[attr] = val; +// } +// return changed; +// }, +// +// // Get the previous value of an attribute, recorded at the time the last +// // `"change"` event was fired. +// previous: function (attr) { +// if (attr == null || !this._previousAttributes) return null; +// return this._previousAttributes[attr]; +// }, +// +// // Get all of the attributes of the model at the time of the previous +// // `"change"` event. +// previousAttributes: function () { +// return _.clone(this._previousAttributes); +// }, +// +// destroy: function (options) { +// options = options ? _.clone(options) : {}; +// var model = this; +// var success = options.success; +// +// var destroy = function () { +// model.stopListening(); +// model.trigger('destroy', model.collection, model, options); +// }; +// +// options.success = function (resp) { +// if (options.wait || model.isNew()) destroy(); +// if (success) success(resp, model, options); +// if (!model.isNew()) model.trigger('sync', resp, model, options).trigger('delete', resp, model, options); +// }; +// +// if (this.isNew()) { +// options.success(); +// return false; +// } +// wrapError(this, options); +// +// var xhr = this.sync('delete', this, options); +// if (!options.wait) destroy(); +// return xhr; +// }, +// });BI.Plugin = BI.Plugin || {}; ; (function () { var _WidgetsPlugin = {}; diff --git a/src/core/m.js b/src/core/m.js new file mode 100644 index 000000000..028ed01b6 --- /dev/null +++ b/src/core/m.js @@ -0,0 +1,178 @@ +// BI.M = BI.inherit(BI.OB, { +// validationError: null, +// +// _init: function () { +// BI.M.superclass._init.apply(this, arguments); +// this.attributes = {}; +// _.extend(this, _.pick(this.options, ['rootURL', 'parent', 'data', 'id'])); +// this.set(this.options); +// this.changed = {}; +// }, +// +// _validate: function (attrs, options) { +// if (!options.validate || !this.validate) return true; +// attrs = _.extend({}, this.attributes, attrs); +// var error = this.validationError = this.validate(attrs, options) || null; +// if (!error) return true; +// this.fireEvent('invalid', error, this, _.extend(options, {validationError: error})); +// return false; +// }, +// +// toJSON: function (options) { +// return _.clone(this.attributes); +// }, +// +// get: function (attr) { +// return this.attributes[attr]; +// }, +// +// has: function (attr) { +// return _.has(this.attributes, attr); +// }, +// +// matches: function (attrs) { +// var keys = _.keys(attrs), length = keys.length; +// var obj = Object(this.attributes); +// for (var i = 0; i < length; i++) { +// var key = keys[i]; +// if (!_.isEqual(attrs[key], obj[key]) || !(key in obj)) return false; +// } +// return true; +// }, +// +// set: function (key, val, options) { +// var attr, attrs, unset, changes, silent, changing, changed, 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 = options; +// for (var i = 0, length = changes.length; i < length; 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; +// changed = BI.clone(this.changed); +// if (!silent) { +// while (this._pending) { +// options = this._pending; +// this._pending = false; +// this.trigger('change', changed, prev, this, options); +// } +// } +// this._pending = false; +// this._changing = false; +// if (!silent && changes.length) this.trigger("changed", changed, prev, this, options); +// return this; +// }, +// +// unset: function (attr, options) { +// return this.set(attr, void 0, _.extend({}, options, {unset: true})); +// }, +// +// clear: function (options) { +// var attrs = {}; +// for (var key in this.attributes) attrs[key] = void 0; +// return this.set(attrs, _.extend({}, options, {unset: true})); +// }, +// +// hasChanged: function (attr) { +// if (attr == null) return !_.isEmpty(this.changed); +// return _.has(this.changed, attr); +// }, +// +// changedAttributes: function (diff) { +// if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; +// var val, changed = false; +// var old = this._changing ? this._previousAttributes : this.attributes; +// for (var attr in diff) { +// if (_.isEqual(old[attr], (val = diff[attr]))) continue; +// (changed || (changed = {}))[attr] = val; +// } +// return changed; +// }, +// +// // Get the previous value of an attribute, recorded at the time the last +// // `"change"` event was fired. +// previous: function (attr) { +// if (attr == null || !this._previousAttributes) return null; +// return this._previousAttributes[attr]; +// }, +// +// // Get all of the attributes of the model at the time of the previous +// // `"change"` event. +// previousAttributes: function () { +// return _.clone(this._previousAttributes); +// }, +// +// destroy: function (options) { +// options = options ? _.clone(options) : {}; +// var model = this; +// var success = options.success; +// +// var destroy = function () { +// model.stopListening(); +// model.trigger('destroy', model.collection, model, options); +// }; +// +// options.success = function (resp) { +// if (options.wait || model.isNew()) destroy(); +// if (success) success(resp, model, options); +// if (!model.isNew()) model.trigger('sync', resp, model, options).trigger('delete', resp, model, options); +// }; +// +// if (this.isNew()) { +// options.success(); +// return false; +// } +// wrapError(this, options); +// +// var xhr = this.sync('delete', this, options); +// if (!options.wait) destroy(); +// return xhr; +// }, +// }); \ No newline at end of file diff --git a/src/core/mvc/fbi.js b/src/core/mvc/fbi.js index 135de6363..a00ec13c7 100644 --- a/src/core/mvc/fbi.js +++ b/src/core/mvc/fbi.js @@ -1,6 +1,6 @@ -(function(root, factory) { - root.BI = factory(root, root.BI || {}, root._, (root.jQuery || root.$)); -}(this, function(root, BI, _, $) { +(function (root, factory) { + root.BI = factory(root, root.BI || {}, root._, (root.jQuery || root.$)); +}(this, function (root, BI, _, $) { var previousBI = root.BI; @@ -17,7 +17,7 @@ // Runs BI.js in *noConflict* mode, returning the `BI` variable // to its previous owner. Returns a reference to this BI object. - BI.noConflict = function() { + BI.noConflict = function () { root.BI = previousBI; return this; }; @@ -50,7 +50,7 @@ // Bind an event to a `callback` function. Passing `"all"` will bind // the callback to all events fired. - on: function(name, callback, context) { + 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] = []); @@ -60,10 +60,10 @@ // 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) { + once: function (name, callback, context) { if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; var self = this; - var once = _.once(function() { + var once = _.once(function () { self.off(name, once); callback.apply(this, arguments); }); @@ -75,7 +75,7 @@ // 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) { + off: function (name, callback, context) { if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; // Remove all callbacks for all events. @@ -106,7 +106,7 @@ callback && callback !== event.callback && callback !== event.callback._callback || context && context !== event.context - ) { + ) { remaining.push(event); } } @@ -122,11 +122,15 @@ return this; }, + un: function () { + this.off.apply(this, arguments); + }, + // 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) { + trigger: function (name) { if (!this._events) return this; var args = slice.call(arguments, 1); if (!eventsApi(this, 'trigger', name, args)) return this; @@ -137,10 +141,14 @@ return this; }, + fireEvent: function () { + this.trigger.apply(this, arguments); + }, + // 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. - listenTo: function(obj, name, callback) { + listenTo: function (obj, name, callback) { var listeningTo = this._listeningTo || (this._listeningTo = {}); var id = obj._listenId || (obj._listenId = _.uniqueId('l')); listeningTo[id] = obj; @@ -149,7 +157,7 @@ return this; }, - listenToOnce: function(obj, name, callback) { + listenToOnce: function (obj, name, callback) { if (typeof name === 'object') { for (var event in name) this.listenToOnce(obj, event, name[event]); return this; @@ -162,7 +170,7 @@ return this; } if (!callback) return this; - var once = _.once(function() { + var once = _.once(function () { this.stopListening(obj, name, once); callback.apply(this, arguments); }); @@ -172,7 +180,7 @@ // Tell this object to stop listening to either specific events ... or // to every object it's currently listening to. - stopListening: function(obj, name, callback) { + stopListening: function (obj, name, callback) { var listeningTo = this._listeningTo; if (!listeningTo) return this; var remove = !name && !callback; @@ -194,7 +202,7 @@ // 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) { + var eventsApi = function (obj, action, name, rest) { if (!name) return true; // Handle event maps. @@ -220,19 +228,29 @@ // A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // BI events have 3 arguments). - var triggerEvents = function(events, args) { + 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); return; + 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); + return; } }; // Aliases for backwards compatibility. - Events.bind = Events.on; + Events.bind = Events.on; Events.unbind = Events.off; // Allow the `BI` object to serve as a global event bus, for folks who @@ -249,7 +267,7 @@ // Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. - var M = BI.M = function(attributes, options) { + var M = BI.M = function (attributes, options) { var attrs = attributes || {}; options = options || {}; this.cid = _.uniqueId('c'); @@ -278,40 +296,47 @@ // CouchDB users may want to set this to `"_id"`. idAttribute: 'ID', - _defaultConfig: function(){return {}}, + _defaultConfig: function () { + return {} + }, + + init: function () { + }, // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + this.init(); + }, // Return a copy of the model's `attributes` object. - toJSON: function(options) { + toJSON: function (options) { return _.clone(this.attributes); }, // Proxy `BI.sync` by default -- but override this if you need // custom syncing semantics for *this* particular model. - sync: function() { + sync: function () { return BI.sync.apply(this, arguments); }, // Get the value of an attribute. - get: function(attr) { + get: function (attr) { return this.attributes[attr]; }, // Get the HTML-escaped value of an attribute. - escape: function(attr) { + 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) { + has: function (attr) { return _.has(this.attributes, attr); }, // Special-cased proxy to underscore's `_.matches` method. - matches: function(attrs) { + matches: function (attrs) { var keys = _.keys(attrs), length = keys.length; var obj = Object(this.attributes); for (var i = 0; i < length; i++) { @@ -324,7 +349,7 @@ // 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) { + set: function (key, val, options) { var attr, attrs, unset, changes, silent, changing, changed, prev, current; if (key == null) return this; @@ -342,11 +367,11 @@ if (!this._validate(attrs, options)) return false; // Extract attributes and options. - unset = options.unset; - silent = options.silent; - changes = []; - changing = this._changing; - this._changing = true; + unset = options.unset; + silent = options.silent; + changes = []; + changing = this._changing; + this._changing = true; if (!changing) { this._previousAttributes = _.clone(this.attributes); @@ -396,12 +421,12 @@ // Remove an attribute from the model, firing `"change"`. `unset` is a noop // if the attribute doesn't exist. - unset: function(attr, options) { + unset: function (attr, options) { return this.set(attr, void 0, _.extend({}, options, {unset: true})); }, // Clear all attributes on the model, firing `"change"`. - clear: function(options) { + clear: function (options) { var attrs = {}; for (var key in this.attributes) attrs[key] = void 0; return this.set(attrs, _.extend({}, options, {unset: true})); @@ -409,7 +434,7 @@ // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. - hasChanged: function(attr) { + hasChanged: function (attr) { if (attr == null) return !_.isEmpty(this.changed); return _.has(this.changed, attr); }, @@ -420,7 +445,7 @@ // persisted to the server. Unset attributes will be set to undefined. // You can also pass an attributes object to diff against the model, // determining if there *would be* a change. - changedAttributes: function(diff) { + changedAttributes: function (diff) { if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; var val, changed = false; var old = this._changing ? this._previousAttributes : this.attributes; @@ -433,27 +458,27 @@ // Get the previous value of an attribute, recorded at the time the last // `"change"` event was fired. - previous: function(attr) { + previous: function (attr) { if (attr == null || !this._previousAttributes) return null; return this._previousAttributes[attr]; }, // Get all of the attributes of the model at the time of the previous // `"change"` event. - previousAttributes: function() { + previousAttributes: function () { return _.clone(this._previousAttributes); }, // Fetch the model from the server. If the server's representation of the // model differs from its current attributes, they will be overridden, // triggering a `"change"` event. - fetch: function(options) { + fetch: function (options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; - options.success = function(resp) { - if(!options.noset) { + options.success = function (resp) { + if (!options.noset) { if (!model.set(model.parse(resp, options), options)) return false; } if (success) success(resp, model, options); @@ -466,7 +491,7 @@ // Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. - save: function(key, val, options) { + save: function (key, val, options) { var attrs, method, xhr, attributes = this.attributes; // Handle both `"key", value` and `{key: value}` -style arguments. @@ -498,7 +523,7 @@ if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; - options.success = function(resp) { + options.success = function (resp) { // Ensure attributes are restored during synchronous saves. model.attributes = attributes; var serverAttrs = model.parse(resp, options); @@ -525,17 +550,17 @@ // Destroy this model on the server if it was already persisted. // Optimistically removes the model from its collection, if it has one. // If `wait: true` is passed, waits for the server to respond before removal. - destroy: function(options) { + destroy: function (options) { options = options ? _.clone(options) : {}; var model = this; var success = options.success; - var destroy = function() { + var destroy = function () { model.stopListening(); model.trigger('destroy', model.collection, model, options); }; - options.success = function(resp) { + options.success = function (resp) { if (options.wait || model.isNew()) destroy(); if (success) success(resp, model, options); if (!model.isNew()) model.trigger('sync', resp, model, options).trigger('delete', resp, model, options); @@ -555,7 +580,7 @@ // Default URL for the model's representation on the server -- if you're // using BI's restful methods, override this to change the endpoint // that will be called. - url: function() { + url: function () { var base = _.result(this.collection, 'url'); if (this.isNew()) return base; @@ -564,28 +589,28 @@ // **parse** converts a response into the hash of attributes to be `set` on // the model. The default implementation is just to pass the response along. - parse: function(resp, options) { + parse: function (resp, options) { return resp; }, // Create a new model with identical attributes to this one. - clone: function() { + clone: function () { return new this.constructor(this.attributes); }, // A model is new if it has never been saved to the server, and lacks an id. - isNew: function() { + isNew: function () { return !this.has(this.idAttribute); }, // Check if the model is currently in a valid state. - isValid: function(options) { - return this._validate({}, _.extend(options || {}, { validate: true })); + isValid: function (options) { + return this._validate({}, _.extend(options || {}, {validate: true})); }, // Run validation against the next complete set of model attributes, // returning `true` if all is well. Otherwise, fire an `"invalid"` event. - _validate: function(attrs, options) { + _validate: function (attrs, options) { if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); var error = this.validationError = this.validate(attrs, options) || null; @@ -600,9 +625,9 @@ var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit', 'chain', 'isEmpty']; // Mix in each Underscore method as a proxy to `M#attributes`. - _.each(modelMethods, function(method) { + _.each(modelMethods, function (method) { if (!_[method]) return; - M.prototype[method] = function() { + M.prototype[method] = function () { var args = slice.call(arguments); args.unshift(this.attributes); return _[method].apply(_, args); @@ -622,7 +647,7 @@ // Create a new **Collection**, perhaps to contain a specific type of `model`. // If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. - var Collection = BI.Collection = function(models, options) { + var Collection = BI.Collection = function (models, options) { this.options = options = options || {}; if (options.model) this.model = options.model; if (options.comparator !== void 0) this.comparator = options.comparator; @@ -644,26 +669,29 @@ // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + }, // The JSON representation of a Collection is an array of the // models' attributes. - toJSON: function(options) { - return this.map(function(model){ return model.toJSON(options); }); + toJSON: function (options) { + return this.map(function (model) { + return model.toJSON(options); + }); }, // Proxy `BI.sync` by default. - sync: function() { + sync: function () { return BI.sync.apply(this, arguments); }, // Add a model, or list of models to the set. - add: function(models, options) { + add: function (models, options) { return this.set(models, _.extend({merge: false}, options, addOptions)); }, // Remove a model, or a list of models from the set. - remove: function(models, options) { + remove: function (models, options) { var singular = !_.isArray(models); models = singular ? [models] : _.clone(models); options || (options = {}); @@ -689,7 +717,7 @@ // removing models that are no longer present, and merging models that // already exist in the collection, as necessary. Similar to **M#set**, // the core operation for updating the data contained by the collection. - set: function(models, options) { + set: function (models, options) { options = _.defaults({}, options, setOptions); if (options.parse) models = this.parse(models, options); var singular = !_.isArray(models); @@ -790,7 +818,7 @@ // you can reset the entire set with a new list of models, without firing // any granular `add` or `remove` events. Fires `reset` when finished. // Useful for bulk operations and optimizations. - reset: function(models, options) { + reset: function (models, options) { options = options ? _.clone(options) : {}; for (var i = 0, length = this.models.length; i < length; i++) { this._removeReference(this.models[i], options); @@ -803,66 +831,66 @@ }, // Add a model to the end of the collection. - push: function(model, options) { + push: function (model, options) { return this.add(model, _.extend({at: this.length}, options)); }, // Remove a model from the end of the collection. - pop: function(options) { + pop: function (options) { var model = this.at(this.length - 1); this.remove(model, options); return model; }, // Add a model to the beginning of the collection. - unshift: function(model, options) { + unshift: function (model, options) { return this.add(model, _.extend({at: 0}, options)); }, // Remove a model from the beginning of the collection. - shift: function(options) { + shift: function (options) { var model = this.at(0); this.remove(model, options); return model; }, // Slice out a sub-array of models from the collection. - slice: function() { + slice: function () { return slice.apply(this.models, arguments); }, // Get a model from the set by id. - get: function(obj) { + get: function (obj) { if (obj == null) return void 0; var id = this.modelId(this._isModel(obj) ? obj.attributes : obj); return this._byId[obj] || this._byId[id] || this._byId[obj.cid]; }, // Get the model at the given index. - at: function(index) { + at: function (index) { if (index < 0) index += this.length; return this.models[index]; }, // Return models with matching attributes. Useful for simple cases of // `filter`. - where: function(attrs, first) { + where: function (attrs, first) { var matches = _.matches(attrs); - return this[first ? 'find' : 'filter'](function(model) { + return this[first ? 'find' : 'filter'](function (model) { return matches(model.attributes); }); }, // Return the first model with matching attributes. Useful for simple cases // of `find`. - findWhere: function(attrs) { + findWhere: function (attrs) { return this.where(attrs, true); }, // Force the collection to re-sort itself. You don't need to call this under // normal circumstances, as the set will maintain sort order as each item // is added. - sort: function(options) { + sort: function (options) { if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); options || (options = {}); @@ -878,19 +906,19 @@ }, // Pluck an attribute from each model in the collection. - pluck: function(attr) { + pluck: function (attr) { return _.invoke(this.models, 'get', attr); }, // Fetch the default set of models for this collection, resetting the // collection when they arrive. If `reset: true` is passed, the response // data will be passed through the `reset` method instead of `set`. - fetch: function(options) { + fetch: function (options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var success = options.success; var collection = this; - options.success = function(resp) { + options.success = function (resp) { var method = options.reset ? 'reset' : 'set'; collection[method](resp, options); if (success) success(collection, resp, options); @@ -903,13 +931,13 @@ // Create a new instance of a model in this collection. Add the model to the // collection immediately, unless `wait: true` is passed, in which case we // wait for the server to agree. - create: function(model, options) { + create: function (model, options) { options = options ? _.clone(options) : {}; if (!(model = this._prepareModel(model, options))) return false; if (!options.wait) this.add(model, options); var collection = this; var success = options.success; - options.success = function(model, resp) { + options.success = function (model, resp) { if (options.wait) collection.add(model, options); if (success) success(model, resp, options); }; @@ -919,12 +947,12 @@ // **parse** converts a response into a list of models to be added to the // collection. The default implementation is just to pass it through. - parse: function(resp, options) { + parse: function (resp, options) { return resp; }, // Create a new collection with an identical list of models as this one. - clone: function() { + clone: function () { return new this.constructor(this.models, { model: this.model, comparator: this.comparator @@ -938,15 +966,15 @@ // Private method to reset all internal state. Called when the collection // is first _initd or reset. - _reset: function() { + _reset: function () { this.length = 0; this.models = []; - this._byId = {}; + this._byId = {}; }, // Prepare a hash of attributes (or other model) to be added to this // collection. - _prepareModel: function(attrs, options) { + _prepareModel: function (attrs, options) { if (this._isModel(attrs)) { if (!attrs.collection) attrs.collection = this; return attrs; @@ -966,7 +994,7 @@ }, // Internal method to create a model's ties to a collection. - _addReference: function(model, options) { + _addReference: function (model, options) { this._byId[model.cid] = model; var id = this.modelId(model.attributes); if (id != null) this._byId[id] = model; @@ -974,7 +1002,7 @@ }, // Internal method to sever a model's ties to a collection. - _removeReference: function(model, options) { + _removeReference: function (model, options) { if (this === model.collection) delete model.collection; model.off('all', this._onModelEvent, this); }, @@ -983,7 +1011,7 @@ // Sets need to update their indexes when models change ids. All other // events simply proxy through. "add" and "remove" events that originate // in other collections are ignored. - _onModelEvent: function(event, model, collection, options) { + _onModelEvent: function (event, model, collection, options) { if ((event === 'add' || event === 'remove') && collection !== this) return; if (event === 'destroy') this.remove(model, options); if (event === 'change') { @@ -1010,9 +1038,9 @@ 'lastIndexOf', 'isEmpty', 'chain', 'sample', 'partition']; // Mix in each Underscore method as a proxy to `Collection#models`. - _.each(methods, function(method) { + _.each(methods, function (method) { if (!_[method]) return; - Collection.prototype[method] = function() { + Collection.prototype[method] = function () { var args = slice.call(arguments); args.unshift(this.models); return _[method].apply(_, args); @@ -1023,10 +1051,10 @@ var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy']; // Use attributes instead of properties. - _.each(attributeMethods, function(method) { + _.each(attributeMethods, function (method) { if (!_[method]) return; - Collection.prototype[method] = function(value, context) { - var iterator = _.isFunction(value) ? value : function(model) { + Collection.prototype[method] = function (value, context) { + var iterator = _.isFunction(value) ? value : function (model) { return model.get(value); }; return _[method](this.models, iterator, context); @@ -1046,7 +1074,7 @@ // Creating a BI.V creates its initial element outside of the DOM, // if an existing element is not provided... - var V = BI.V = function(options) { + var V = BI.V = function (options) { this.cid = _.uniqueId('view'); options = options || {}; this.options = _.defaults(options, _.result(this, '_defaultConfig')); @@ -1069,28 +1097,33 @@ // jQuery delegate for element lookup, scoped to DOM elements within the // current view. This should be preferred to global lookups where possible. - $: function(selector) { + $: function (selector) { return this.$el.find(selector); }, - _defaultConfig: function(){return {}}, + _defaultConfig: function () { + return {} + }, // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + }, //容器,默认放在this.element上 - _vessel: function(){return this}, + _vessel: function () { + return this + }, // **render** is the core function that your view should override, in order // to populate its element (`this.el`), with the appropriate HTML. The // convention is for **render** to always return `this`. - _render: function(vessel) { + render: function (vessel) { return this; }, // Remove this view by taking the element out of the DOM, and removing any // applicable BI.Events listeners. - remove: function() { + remove: function () { this._removeElement(); this.stopListening(); return this; @@ -1099,7 +1132,7 @@ // Remove this view's element from the document and all event listeners // attached to it. Exposed for subclasses using an alternative DOM // manipulation API. - _removeElement: function() { + _removeElement: function () { this.$el.remove(); if ($.browser.msie === true) { this.el.outerHTML = ''; @@ -1108,33 +1141,33 @@ // Change the view's element (`this.el` property) and re-delegate the // view's events on the new element. - setElement: function(element) { + setElement: function (element) { this.undelegateEvents(); this._setElement(element); - this.$vessel = this._vessel(); - this._render(this.$vessel); + this.vessel = this._vessel(); + this.render(this.vessel); this.delegateEvents(); return this; }, - setVisible: function(visible){ + setVisible: function (visible) { this.options.invisible = !visible; - if (visible){ + if (visible) { this.element.show(); } else { this.element.hide(); } }, - isVisible: function(){ + isVisible: function () { return !this.options.invisible; }, - visible: function(){ + visible: function () { this.setVisible(true); }, - invisible: function(){ + invisible: function () { this.setVisible(false); }, @@ -1143,7 +1176,7 @@ // context or an element. Subclasses can override this to utilize an // alternative DOM manipulation API and are only required to set the // `this.el` property. - _setElement: function(el) { + _setElement: function (el) { this.$el = el instanceof BI.$ ? el : BI.$(el); this.element = this.$el; this.el = this.$el[0]; @@ -1162,7 +1195,7 @@ // pairs. Callbacks will be bound to the view, with `this` set properly. // Uses event delegation for efficiency. // Omitting the selector binds the event to `this.el`. - delegateEvents: function(events) { + delegateEvents: function (events) { if (!(events || (events = _.result(this, 'events')))) return this; this.undelegateEvents(); for (var key in events) { @@ -1178,27 +1211,27 @@ // Add a single event listener to the view's element (or a child element // using `selector`). This only works for delegate-able events: not `focus`, // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer. - delegate: function(eventName, selector, listener) { - this.$vessel.element.on(eventName + '.delegateEvents' + this.cid, selector, listener); + delegate: function (eventName, selector, listener) { + this.vessel.element.on(eventName + '.delegateEvents' + this.cid, selector, listener); }, // Clears all callbacks previously bound to the view by `delegateEvents`. // You usually don't need to use this, but may wish to if you have multiple // BI views attached to the same DOM element. - undelegateEvents: function() { - if (this.$vessel) this.$vessel.element.off('.delegateEvents' + this.cid); + undelegateEvents: function () { + if (this.vessel) this.vessel.element.off('.delegateEvents' + this.cid); return this; }, // A finer-grained `undelegateEvents` for removing a single delegated event. // `selector` and `listener` are both optional. - undelegate: function(eventName, selector, listener) { - this.$vessel.element.off(eventName + '.delegateEvents' + this.cid, selector, listener); + undelegate: function (eventName, selector, listener) { + this.vessel.element.off(eventName + '.delegateEvents' + this.cid, selector, listener); }, // Produces a DOM element to be assigned to your view. Exposed for // subclasses using an alternative DOM manipulation API. - _createElement: function(tagName) { + _createElement: function (tagName) { return document.createElement(tagName); }, @@ -1206,7 +1239,7 @@ // If `this.el` is a string, pass it through `$()`, take the first // matching element, and re-assign it to `el`. Otherwise, create // an element from the `id`, `className` and `tagName` properties. - _ensureElement: function() { + _ensureElement: function () { var attrs = _.extend({}, _.result(this, 'attributes')); if (this.baseCls) attrs['class'] = _.result(this, 'baseCls'); if (!this.element) { @@ -1219,7 +1252,7 @@ // Set attributes from a hash on this view's element. Exposed for // subclasses using an alternative DOM manipulation API. - _setAttributes: function(attributes) { + _setAttributes: function (attributes) { this.$el.attr(attributes); } @@ -1243,7 +1276,7 @@ // instead of `application/json` with the model in a param named `model`. // Useful when interfacing with server-side languages like **PHP** that make // it difficult to read the body of `PUT` requests. - BI.sync = function(method, model, options) { + BI.sync = function (method, model, options) { var type = methodMap[method]; // Default options, unless specified. @@ -1257,8 +1290,8 @@ // Ensure that we have a URL. if (!options.url) { - params.url = _.result(model, method+"URL") || _.result(model, 'url'); - if(!params.url){ + params.url = _.result(model, method + "URL") || _.result(model, 'url'); + if (!params.url) { return; } } @@ -1281,7 +1314,7 @@ params.type = 'POST'; if (options.emulateJSON) params.data._method = type; var beforeSend = options.beforeSend; - options.beforeSend = function(xhr) { + options.beforeSend = function (xhr) { xhr.setRequestHeader('X-HTTP-Method-Override', type); if (beforeSend) return beforeSend.apply(this, arguments); }; @@ -1294,7 +1327,7 @@ // Pass along `textStatus` and `errorThrown` from jQuery. var error = options.error; - options.error = function(xhr, textStatus, errorThrown) { + options.error = function (xhr, textStatus, errorThrown) { options.textStatus = textStatus; options.errorThrown = errorThrown; if (error) error.apply(this, arguments); @@ -1310,9 +1343,9 @@ var methodMap = { 'create': 'POST', 'update': 'PUT', - 'patch': 'PATCH', + 'patch': 'PATCH', 'delete': 'DELETE', - 'read': 'GET' + 'read': 'GET' }; // Set the default implementation of `BI.ajax` to proxy through to `$`. @@ -1324,7 +1357,7 @@ // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. - var Router = BI.Router = function(options) { + var Router = BI.Router = function (options) { options || (options = {}); if (options.routes) this.routes = options.routes; this._bindRoutes(); @@ -1334,16 +1367,17 @@ // Cached regular expressions for matching named param parts and splatted // parts of route strings. var optionalParam = /\((.*?)\)/g; - var namedParam = /(\(\?)?:\w+/g; - var splatParam = /\*\w+/g; - var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; + var namedParam = /(\(\?)?:\w+/g; + var splatParam = /\*\w+/g; + var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; // Set up all inheritable **BI.Router** properties and methods. _.extend(Router.prototype, Events, { // _init is an empty function by default. Override it with your own // initialization logic. - _init: function(){}, + _init: function () { + }, // Manually bind a single named route to a callback. For example: // @@ -1351,7 +1385,7 @@ // ... // }); // - route: function(route, name, callback) { + route: function (route, name, callback) { if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (_.isFunction(name)) { callback = name; @@ -1359,7 +1393,7 @@ } if (!callback) callback = this[name]; var router = this; - BI.history.route(route, function(fragment) { + BI.history.route(route, function (fragment) { var args = router._extractParameters(route, fragment); if (router.execute(callback, args, name) !== false) { router.trigger.apply(router, ['route:' + name].concat(args)); @@ -1372,12 +1406,12 @@ // Execute a route handler with the provided parameters. This is an // excellent place to do pre-route setup or post-route cleanup. - execute: function(callback, args, name) { + execute: function (callback, args, name) { if (callback) callback.apply(this, args); }, // Simple proxy to `BI.history` to save a fragment into the history. - navigate: function(fragment, options) { + navigate: function (fragment, options) { BI.history.navigate(fragment, options); return this; }, @@ -1385,7 +1419,7 @@ // Bind all defined routes to `BI.history`. We have to reverse the // order of the routes here to support behavior where the most general // routes can be defined at the bottom of the route map. - _bindRoutes: function() { + _bindRoutes: function () { if (!this.routes) return; this.routes = _.result(this, 'routes'); var route, routes = _.keys(this.routes); @@ -1396,10 +1430,10 @@ // Convert a route string into a regular expression, suitable for matching // against the current location hash. - _routeToRegExp: function(route) { + _routeToRegExp: function (route) { route = route.replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') - .replace(namedParam, function(match, optional) { + .replace(namedParam, function (match, optional) { return optional ? match : '([^/?]+)'; }) .replace(splatParam, '([^?]*?)'); @@ -1409,9 +1443,9 @@ // Given a route, and a URL fragment that it matches, return the array of // extracted decoded parameters. Empty or unmatched parameters will be // treated as `null` to normalize cross-browser behavior. - _extractParameters: function(route, fragment) { + _extractParameters: function (route, fragment) { var params = route.exec(fragment).slice(1); - return _.map(params, function(param, i) { + return _.map(params, function (param, i) { // Don't decode the search params. if (i === params.length - 1) return param || null; return param ? decodeURIComponent(param) : null; @@ -1428,7 +1462,7 @@ // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) // and URL fragments. If the browser supports neither (old IE, natch), // falls back to polling. - var History = BI.History = function() { + var History = BI.History = function () { this.handlers = []; _.bindAll(this, 'checkUrl'); @@ -1459,27 +1493,27 @@ interval: 50, // Are we at the app root? - atRoot: function() { + atRoot: function () { var path = this.location.pathname.replace(/[^\/]$/, '$&/'); return path === this.root && !this.getSearch(); }, // In IE6, the hash fragment and search params are incorrect if the // fragment contains `?`. - getSearch: function() { + getSearch: function () { var match = this.location.href.replace(/#.*/, '').match(/\?.+/); return match ? match[0] : ''; }, // Gets the true hash value. Cannot use location.hash directly due to bug // in Firefox where location.hash will always be decoded. - getHash: function(window) { + getHash: function (window) { var match = (window || this).location.href.match(/#(.*)$/); return match ? match[1] : ''; }, // Get the pathname and search params, without the root. - getPath: function() { + getPath: function () { var path = decodeURI(this.location.pathname + this.getSearch()); var root = this.root.slice(0, -1); if (!path.indexOf(root)) path = path.slice(root.length); @@ -1487,7 +1521,7 @@ }, // Get the cross-browser normalized URL fragment from the path or hash. - getFragment: function(fragment) { + getFragment: function (fragment) { if (fragment == null) { if (this._hasPushState || !this._wantsHashChange) { fragment = this.getPath(); @@ -1500,19 +1534,19 @@ // Start the hash change handling, returning `true` if the current URL matches // an existing route, and `false` otherwise. - start: function(options) { + start: function (options) { if (History.started) throw new Error('BI.history has already been started'); History.started = true; // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? - this.options = _.extend({root: '/'}, this.options, options); - this.root = this.options.root; + this.options = _.extend({root: '/'}, this.options, options); + this.root = this.options.root; this._wantsHashChange = this.options.hashChange !== false; - this._hasHashChange = 'onhashchange' in window; - this._wantsPushState = !!this.options.pushState; - this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); - this.fragment = this.getFragment(); + this._hasHashChange = 'onhashchange' in window; + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); + this.fragment = this.getFragment(); // Normalize root to always include a leading and trailing slash. this.root = ('/' + this.root + '/').replace(rootStripper, '/'); @@ -1554,8 +1588,8 @@ // Add a cross-platform `addEventListener` shim for older browsers. var addEventListener = window.addEventListener || function (eventName, listener) { - return attachEvent('on' + eventName, listener); - }; + return attachEvent('on' + eventName, listener); + }; // Depending on whether we're using pushState or hashes, and whether // 'onhashchange' is supported, determine how we check the URL state. @@ -1572,11 +1606,11 @@ // Disable BI.history, perhaps temporarily. Not useful in a real app, // but possibly useful for unit testing Routers. - stop: function() { + stop: function () { // Add a cross-platform `removeEventListener` shim for older browsers. var removeEventListener = window.removeEventListener || function (eventName, listener) { - return detachEvent('on' + eventName, listener); - }; + return detachEvent('on' + eventName, listener); + }; // Remove window listeners. if (this._hasPushState) { @@ -1598,13 +1632,13 @@ // Add a route to be tested when the fragment changes. Routes added later // may override previous routes. - route: function(route, callback) { + route: function (route, callback) { this.handlers.unshift({route: route, callback: callback}); }, // Checks the current URL to see if it has changed, and if it has, // calls `loadUrl`, normalizing across the hidden iframe. - checkUrl: function(e) { + checkUrl: function (e) { var current = this.getFragment(); // If the user pressed the back button, the iframe's hash will have @@ -1621,9 +1655,9 @@ // Attempt to load the current URL fragment. If a route succeeds with a // match, returns `true`. If no defined routes matches the fragment, // returns `false`. - loadUrl: function(fragment) { + loadUrl: function (fragment) { fragment = this.fragment = this.getFragment(fragment); - return _.any(this.handlers, function(handler) { + return _.any(this.handlers, function (handler) { if (handler.route.test(fragment)) { handler.callback(fragment); return true; @@ -1638,7 +1672,7 @@ // The options object can contain `trigger: true` if you wish to have the // route callback be fired (not usually desirable), or `replace: true`, if // you wish to modify the current URL without adding an entry to the history. - navigate: function(fragment, options) { + navigate: function (fragment, options) { if (!History.started) return false; if (!options || options === true) options = {trigger: !!options}; @@ -1684,7 +1718,7 @@ // Update the hash location, either replacing the current entry, or adding // a new one to the browser history. - _updateHash: function(location, fragment, replace) { + _updateHash: function (location, fragment, replace) { if (replace) { var href = location.href.replace(/(javascript:|#).*$/, ''); location.replace(href + '#' + fragment); @@ -1705,7 +1739,7 @@ // Helper function to correctly set up the prototype chain, for subclasses. // Similar to `goog.inherits`, but uses a hash of prototype properties and // class properties to be extended. - var extend = function(protoProps, staticProps) { + var extend = function (protoProps, staticProps) { var parent = this; var child; @@ -1715,7 +1749,9 @@ if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { - child = function(){ return parent.apply(this, arguments); }; + child = function () { + return parent.apply(this, arguments); + }; } // Add static properties to the constructor function, if supplied. @@ -1723,7 +1759,9 @@ // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. - var Surrogate = function(){ this.constructor = child; }; + var Surrogate = function () { + this.constructor = child; + }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate; @@ -1742,14 +1780,14 @@ M.extend = Collection.extend = Router.extend = V.extend = History.extend = extend; // Throw an error when a URL is needed, and none is supplied. - var urlError = function() { + var urlError = function () { throw new Error('A "url" property or function must be specified'); }; // Wrap an optional error callback with a fallback error event. - var wrapError = function(model, options) { + var wrapError = function (model, options) { var error = options.error; - options.error = function(resp) { + options.error = function (resp) { if (error) error(model, resp, options); model.trigger('error', model, resp, options); };