PageRenderTime 99ms CodeModel.GetById 31ms RepoModel.GetById 2ms app.codeStats 0ms

/src/main/webapp/js/libs/backbone-0.9.2.js

https://bitbucket.org/jtorelli/intersource-ticketing-system-demo
JavaScript | 1466 lines | 856 code | 224 blank | 386 comment | 286 complexity | dd3584481f69b1cd252e1311a7121343 MD5 | raw file
Possible License(s): GPL-2.0, MIT

Large files files are truncated, but you can click here to view the full file

  1. // Backbone.js 0.9.2
  2. // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
  3. // Backbone may be freely distributed under the MIT license.
  4. // For all details and documentation:
  5. // http://backbonejs.org
  6. (function(){
  7. // Initial Setup
  8. // -------------
  9. // Save a reference to the global object (`window` in the browser, `global`
  10. // on the server).
  11. var root = this;
  12. // Save the previous value of the `Backbone` variable, so that it can be
  13. // restored later on, if `noConflict` is used.
  14. var previousBackbone = root.Backbone;
  15. // Create a local reference to array methods.
  16. var ArrayProto = Array.prototype;
  17. var push = ArrayProto.push;
  18. var slice = ArrayProto.slice;
  19. var splice = ArrayProto.splice;
  20. // The top-level namespace. All public Backbone classes and modules will
  21. // be attached to this. Exported for both CommonJS and the browser.
  22. var Backbone;
  23. if (typeof exports !== 'undefined') {
  24. Backbone = exports;
  25. } else {
  26. Backbone = root.Backbone = {};
  27. }
  28. // Current version of the library. Keep in sync with `package.json`.
  29. Backbone.VERSION = '0.9.2';
  30. // Require Underscore, if we're on the server, and it's not already present.
  31. var _ = root._;
  32. if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
  33. // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
  34. Backbone.$ = root.jQuery || root.Zepto || root.ender;
  35. // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
  36. // to its previous owner. Returns a reference to this Backbone object.
  37. Backbone.noConflict = function() {
  38. root.Backbone = previousBackbone;
  39. return this;
  40. };
  41. // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
  42. // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
  43. // set a `X-Http-Method-Override` header.
  44. Backbone.emulateHTTP = false;
  45. // Turn on `emulateJSON` to support legacy servers that can't deal with direct
  46. // `application/json` requests ... will encode the body as
  47. // `application/x-www-form-urlencoded` instead and will send the model in a
  48. // form param named `model`.
  49. Backbone.emulateJSON = false;
  50. // Backbone.Events
  51. // -----------------
  52. // Regular expression used to split event strings
  53. var eventSplitter = /\s+/;
  54. // A module that can be mixed in to *any object* in order to provide it with
  55. // custom events. You may bind with `on` or remove with `off` callback functions
  56. // to an event; `trigger`-ing an event fires all callbacks in succession.
  57. //
  58. // var object = {};
  59. // _.extend(object, Backbone.Events);
  60. // object.on('expand', function(){ alert('expanded'); });
  61. // object.trigger('expand');
  62. //
  63. var Events = Backbone.Events = {
  64. // Bind one or more space separated events, `events`, to a `callback`
  65. // function. Passing `"all"` will bind the callback to all events fired.
  66. on: function(events, callback, context) {
  67. var calls, event, list;
  68. if (!callback) return this;
  69. events = events.split(eventSplitter);
  70. calls = this._callbacks || (this._callbacks = {});
  71. while (event = events.shift()) {
  72. list = calls[event] || (calls[event] = []);
  73. list.push(callback, context);
  74. }
  75. return this;
  76. },
  77. // Remove one or many callbacks. If `context` is null, removes all callbacks
  78. // with that function. If `callback` is null, removes all callbacks for the
  79. // event. If `events` is null, removes all bound callbacks for all events.
  80. off: function(events, callback, context) {
  81. var event, calls, list, i;
  82. // No events, or removing *all* events.
  83. if (!(calls = this._callbacks)) return this;
  84. if (!(events || callback || context)) {
  85. delete this._callbacks;
  86. return this;
  87. }
  88. events = events ? events.split(eventSplitter) : _.keys(calls);
  89. // Loop through the callback list, splicing where appropriate.
  90. while (event = events.shift()) {
  91. if (!(list = calls[event]) || !(callback || context)) {
  92. delete calls[event];
  93. continue;
  94. }
  95. for (i = list.length - 2; i >= 0; i -= 2) {
  96. if (!(callback && list[i] !== callback || context && list[i + 1] !== context)) {
  97. list.splice(i, 2);
  98. }
  99. }
  100. }
  101. return this;
  102. },
  103. // Trigger one or many events, firing all bound callbacks. Callbacks are
  104. // passed the same arguments as `trigger` is, apart from the event name
  105. // (unless you're listening on `"all"`, which will cause your callback to
  106. // receive the true name of the event as the first argument).
  107. trigger: function(events) {
  108. var event, calls, list, i, length, args, all, rest;
  109. if (!(calls = this._callbacks)) return this;
  110. rest = [];
  111. events = events.split(eventSplitter);
  112. // Fill up `rest` with the callback arguments. Since we're only copying
  113. // the tail of `arguments`, a loop is much faster than Array#slice.
  114. for (i = 1, length = arguments.length; i < length; i++) {
  115. rest[i - 1] = arguments[i];
  116. }
  117. // For each event, walk through the list of callbacks twice, first to
  118. // trigger the event, then to trigger any `"all"` callbacks.
  119. while (event = events.shift()) {
  120. // Copy callback lists to prevent modification.
  121. if (all = calls.all) all = all.slice();
  122. if (list = calls[event]) list = list.slice();
  123. // Execute event callbacks.
  124. if (list) {
  125. for (i = 0, length = list.length; i < length; i += 2) {
  126. list[i].apply(list[i + 1] || this, rest);
  127. }
  128. }
  129. // Execute "all" callbacks.
  130. if (all) {
  131. args = [event].concat(rest);
  132. for (i = 0, length = all.length; i < length; i += 2) {
  133. all[i].apply(all[i + 1] || this, args);
  134. }
  135. }
  136. }
  137. return this;
  138. }
  139. };
  140. // Aliases for backwards compatibility.
  141. Events.bind = Events.on;
  142. Events.unbind = Events.off;
  143. // Backbone.Model
  144. // --------------
  145. // Create a new model, with defined attributes. A client id (`cid`)
  146. // is automatically generated and assigned for you.
  147. var Model = Backbone.Model = function(attributes, options) {
  148. var defaults;
  149. attributes || (attributes = {});
  150. if (options && options.collection) this.collection = options.collection;
  151. if (options && options.parse) attributes = this.parse(attributes);
  152. if (defaults = _.result(this, 'defaults')) {
  153. attributes = _.extend({}, defaults, attributes);
  154. }
  155. this.attributes = {};
  156. this._escapedAttributes = {};
  157. this.cid = _.uniqueId('c');
  158. this.changed = {};
  159. this._silent = {};
  160. this._pending = {};
  161. this.set(attributes, {silent: true});
  162. // Reset change tracking.
  163. this.changed = {};
  164. this._silent = {};
  165. this._pending = {};
  166. this._previousAttributes = _.clone(this.attributes);
  167. this.initialize.apply(this, arguments);
  168. };
  169. // Attach all inheritable methods to the Model prototype.
  170. _.extend(Model.prototype, Events, {
  171. // A hash of attributes whose current and previous value differ.
  172. changed: null,
  173. // A hash of attributes that have silently changed since the last time
  174. // `change` was called. Will become pending attributes on the next call.
  175. _silent: null,
  176. // A hash of attributes that have changed since the last `'change'` event
  177. // began.
  178. _pending: null,
  179. // The default name for the JSON `id` attribute is `"id"`. MongoDB and
  180. // CouchDB users may want to set this to `"_id"`.
  181. idAttribute: 'id',
  182. // Initialize is an empty function by default. Override it with your own
  183. // initialization logic.
  184. initialize: function(){},
  185. // Return a copy of the model's `attributes` object.
  186. toJSON: function(options) {
  187. return _.clone(this.attributes);
  188. },
  189. // Proxy `Backbone.sync` by default.
  190. sync: function() {
  191. return Backbone.sync.apply(this, arguments);
  192. },
  193. // Get the value of an attribute.
  194. get: function(attr) {
  195. return this.attributes[attr];
  196. },
  197. // Get the HTML-escaped value of an attribute.
  198. escape: function(attr) {
  199. var html;
  200. if (html = this._escapedAttributes[attr]) return html;
  201. var val = this.get(attr);
  202. return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
  203. },
  204. // Returns `true` if the attribute contains a value that is not null
  205. // or undefined.
  206. has: function(attr) {
  207. return this.get(attr) != null;
  208. },
  209. // Set a hash of model attributes on the object, firing `"change"` unless
  210. // you choose to silence it.
  211. set: function(key, value, options) {
  212. var attrs, attr, val;
  213. // Handle both `"key", value` and `{key: value}` -style arguments.
  214. if (_.isObject(key) || key == null) {
  215. attrs = key;
  216. options = value;
  217. } else {
  218. attrs = {};
  219. attrs[key] = value;
  220. }
  221. // Extract attributes and options.
  222. options || (options = {});
  223. if (!attrs) return this;
  224. if (attrs instanceof Model) attrs = attrs.attributes;
  225. if (options.unset) for (attr in attrs) attrs[attr] = void 0;
  226. // Run validation.
  227. if (!this._validate(attrs, options)) return false;
  228. // Check for changes of `id`.
  229. if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
  230. var changes = options.changes = {};
  231. var now = this.attributes;
  232. var escaped = this._escapedAttributes;
  233. var prev = this._previousAttributes || {};
  234. // For each `set` attribute...
  235. for (attr in attrs) {
  236. val = attrs[attr];
  237. // If the new and current value differ, record the change.
  238. if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
  239. delete escaped[attr];
  240. (options.silent ? this._silent : changes)[attr] = true;
  241. }
  242. // Update or delete the current value.
  243. options.unset ? delete now[attr] : now[attr] = val;
  244. // If the new and previous value differ, record the change. If not,
  245. // then remove changes for this attribute.
  246. if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) {
  247. this.changed[attr] = val;
  248. if (!options.silent) this._pending[attr] = true;
  249. } else {
  250. delete this.changed[attr];
  251. delete this._pending[attr];
  252. }
  253. }
  254. // Fire the `"change"` events.
  255. if (!options.silent) this.change(options);
  256. return this;
  257. },
  258. // Remove an attribute from the model, firing `"change"` unless you choose
  259. // to silence it. `unset` is a noop if the attribute doesn't exist.
  260. unset: function(attr, options) {
  261. options = _.extend({}, options, {unset: true});
  262. return this.set(attr, null, options);
  263. },
  264. // Clear all attributes on the model, firing `"change"` unless you choose
  265. // to silence it.
  266. clear: function(options) {
  267. options = _.extend({}, options, {unset: true});
  268. return this.set(_.clone(this.attributes), options);
  269. },
  270. // Fetch the model from the server. If the server's representation of the
  271. // model differs from its current attributes, they will be overriden,
  272. // triggering a `"change"` event.
  273. fetch: function(options) {
  274. options = options ? _.clone(options) : {};
  275. var model = this;
  276. var success = options.success;
  277. options.success = function(resp, status, xhr) {
  278. if (!model.set(model.parse(resp, xhr), options)) return false;
  279. if (success) success(model, resp, options);
  280. };
  281. return this.sync('read', this, options);
  282. },
  283. // Set a hash of model attributes, and sync the model to the server.
  284. // If the server returns an attributes hash that differs, the model's
  285. // state will be `set` again.
  286. save: function(key, value, options) {
  287. var attrs, current, done;
  288. // Handle both `("key", value)` and `({key: value})` -style calls.
  289. if (_.isObject(key) || key == null) {
  290. attrs = key;
  291. options = value;
  292. } else {
  293. attrs = {};
  294. attrs[key] = value;
  295. }
  296. options = options ? _.clone(options) : {};
  297. // If we're "wait"-ing to set changed attributes, validate early.
  298. if (options.wait) {
  299. if (!this._validate(attrs, options)) return false;
  300. current = _.clone(this.attributes);
  301. }
  302. // Regular saves `set` attributes before persisting to the server.
  303. var silentOptions = _.extend({}, options, {silent: true});
  304. if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
  305. return false;
  306. }
  307. // Do not persist invalid models.
  308. if (!attrs && !this.isValid()) return false;
  309. // After a successful server-side save, the client is (optionally)
  310. // updated with the server-side state.
  311. var model = this;
  312. var success = options.success;
  313. options.success = function(resp, status, xhr) {
  314. done = true;
  315. var serverAttrs = model.parse(resp, xhr);
  316. if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
  317. if (!model.set(serverAttrs, options)) return false;
  318. if (success) success(model, resp, options);
  319. };
  320. // Finish configuring and sending the Ajax request.
  321. var xhr = this.sync(this.isNew() ? 'create' : 'update', this, options);
  322. // When using `wait`, reset attributes to original values unless
  323. // `success` has been called already.
  324. if (!done && options.wait) {
  325. this.clear(silentOptions);
  326. this.set(current, silentOptions);
  327. }
  328. return xhr;
  329. },
  330. // Destroy this model on the server if it was already persisted.
  331. // Optimistically removes the model from its collection, if it has one.
  332. // If `wait: true` is passed, waits for the server to respond before removal.
  333. destroy: function(options) {
  334. options = options ? _.clone(options) : {};
  335. var model = this;
  336. var success = options.success;
  337. var destroy = function() {
  338. model.trigger('destroy', model, model.collection, options);
  339. };
  340. options.success = function(resp) {
  341. if (options.wait || model.isNew()) destroy();
  342. if (success) success(model, resp, options);
  343. };
  344. if (this.isNew()) {
  345. options.success();
  346. return false;
  347. }
  348. var xhr = this.sync('delete', this, options);
  349. if (!options.wait) destroy();
  350. return xhr;
  351. },
  352. // Default URL for the model's representation on the server -- if you're
  353. // using Backbone's restful methods, override this to change the endpoint
  354. // that will be called.
  355. url: function() {
  356. var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
  357. if (this.isNew()) return base;
  358. return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
  359. },
  360. // **parse** converts a response into the hash of attributes to be `set` on
  361. // the model. The default implementation is just to pass the response along.
  362. parse: function(resp, xhr) {
  363. return resp;
  364. },
  365. // Create a new model with identical attributes to this one.
  366. clone: function() {
  367. return new this.constructor(this.attributes);
  368. },
  369. // A model is new if it has never been saved to the server, and lacks an id.
  370. isNew: function() {
  371. return this.id == null;
  372. },
  373. // Call this method to manually fire a `"change"` event for this model and
  374. // a `"change:attribute"` event for each changed attribute.
  375. // Calling this will cause all objects observing the model to update.
  376. change: function(options) {
  377. options || (options = {});
  378. var changing = this._changing;
  379. this._changing = true;
  380. // Silent changes become pending changes.
  381. for (var attr in this._silent) this._pending[attr] = true;
  382. // Silent changes are triggered.
  383. var changes = _.extend({}, options.changes, this._silent);
  384. this._silent = {};
  385. for (var attr in changes) {
  386. this.trigger('change:' + attr, this, this.get(attr), options);
  387. }
  388. if (changing) return this;
  389. // Continue firing `"change"` events while there are pending changes.
  390. while (!_.isEmpty(this._pending)) {
  391. this._pending = {};
  392. this.trigger('change', this, options);
  393. // Pending and silent changes still remain.
  394. for (var attr in this.changed) {
  395. if (this._pending[attr] || this._silent[attr]) continue;
  396. delete this.changed[attr];
  397. }
  398. this._previousAttributes = _.clone(this.attributes);
  399. }
  400. this._changing = false;
  401. return this;
  402. },
  403. // Determine if the model has changed since the last `"change"` event.
  404. // If you specify an attribute name, determine if that attribute has changed.
  405. hasChanged: function(attr) {
  406. if (attr == null) return !_.isEmpty(this.changed);
  407. return _.has(this.changed, attr);
  408. },
  409. // Return an object containing all the attributes that have changed, or
  410. // false if there are no changed attributes. Useful for determining what
  411. // parts of a view need to be updated and/or what attributes need to be
  412. // persisted to the server. Unset attributes will be set to undefined.
  413. // You can also pass an attributes object to diff against the model,
  414. // determining if there *would be* a change.
  415. changedAttributes: function(diff) {
  416. if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
  417. var val, changed = false, old = this._previousAttributes;
  418. for (var attr in diff) {
  419. if (_.isEqual(old[attr], (val = diff[attr]))) continue;
  420. (changed || (changed = {}))[attr] = val;
  421. }
  422. return changed;
  423. },
  424. // Get the previous value of an attribute, recorded at the time the last
  425. // `"change"` event was fired.
  426. previous: function(attr) {
  427. if (attr == null || !this._previousAttributes) return null;
  428. return this._previousAttributes[attr];
  429. },
  430. // Get all of the attributes of the model at the time of the previous
  431. // `"change"` event.
  432. previousAttributes: function() {
  433. return _.clone(this._previousAttributes);
  434. },
  435. // Check if the model is currently in a valid state. It's only possible to
  436. // get into an *invalid* state if you're using silent changes.
  437. isValid: function(options) {
  438. return !this.validate || !this.validate(this.attributes, options);
  439. },
  440. // Run validation against the next complete set of model attributes,
  441. // returning `true` if all is well. If a specific `error` callback has
  442. // been passed, call that instead of firing the general `"error"` event.
  443. _validate: function(attrs, options) {
  444. if (options.silent || !this.validate) return true;
  445. attrs = _.extend({}, this.attributes, attrs);
  446. var error = this.validate(attrs, options);
  447. if (!error) return true;
  448. if (options && options.error) options.error(this, error, options);
  449. this.trigger('error', this, error, options);
  450. return false;
  451. }
  452. });
  453. // Backbone.Collection
  454. // -------------------
  455. // Provides a standard collection class for our sets of models, ordered
  456. // or unordered. If a `comparator` is specified, the Collection will maintain
  457. // its models in sort order, as they're added and removed.
  458. var Collection = Backbone.Collection = function(models, options) {
  459. options || (options = {});
  460. if (options.model) this.model = options.model;
  461. if (options.comparator !== void 0) this.comparator = options.comparator;
  462. this._reset();
  463. this.initialize.apply(this, arguments);
  464. if (models) {
  465. if (options.parse) models = this.parse(models);
  466. this.reset(models, {silent: true, parse: options.parse});
  467. }
  468. };
  469. // Define the Collection's inheritable methods.
  470. _.extend(Collection.prototype, Events, {
  471. // The default model for a collection is just a **Backbone.Model**.
  472. // This should be overridden in most cases.
  473. model: Model,
  474. // Initialize is an empty function by default. Override it with your own
  475. // initialization logic.
  476. initialize: function(){},
  477. // The JSON representation of a Collection is an array of the
  478. // models' attributes.
  479. toJSON: function(options) {
  480. return this.map(function(model){ return model.toJSON(options); });
  481. },
  482. // Proxy `Backbone.sync` by default.
  483. sync: function() {
  484. return Backbone.sync.apply(this, arguments);
  485. },
  486. // Add a model, or list of models to the set. Pass **silent** to avoid
  487. // firing the `add` event for every new model.
  488. add: function(models, options) {
  489. var i, args, length, model, existing;
  490. var at = options && options.at;
  491. models = _.isArray(models) ? models.slice() : [models];
  492. // Begin by turning bare objects into model references, and preventing
  493. // invalid models from being added.
  494. for (i = 0, length = models.length; i < length; i++) {
  495. if (models[i] = this._prepareModel(models[i], options)) continue;
  496. throw new Error("Can't add an invalid model to a collection");
  497. }
  498. for (i = models.length - 1; i >= 0; i--) {
  499. model = models[i];
  500. existing = model.id != null && this._byId[model.id];
  501. // If a duplicate is found, splice it out and optionally merge it into
  502. // the existing model.
  503. if (existing || this._byCid[model.cid]) {
  504. if (options && options.merge && existing) {
  505. existing.set(model, options);
  506. }
  507. models.splice(i, 1);
  508. continue;
  509. }
  510. // Listen to added models' events, and index models for lookup by
  511. // `id` and by `cid`.
  512. model.on('all', this._onModelEvent, this);
  513. this._byCid[model.cid] = model;
  514. if (model.id != null) this._byId[model.id] = model;
  515. }
  516. // Update `length` and splice in new models.
  517. this.length += models.length;
  518. args = [at != null ? at : this.models.length, 0];
  519. push.apply(args, models);
  520. splice.apply(this.models, args);
  521. // Sort the collection if appropriate.
  522. if (this.comparator && at == null) this.sort({silent: true});
  523. if (options && options.silent) return this;
  524. // Trigger `add` events.
  525. while (model = models.shift()) {
  526. model.trigger('add', model, this, options);
  527. }
  528. return this;
  529. },
  530. // Remove a model, or a list of models from the set. Pass silent to avoid
  531. // firing the `remove` event for every model removed.
  532. remove: function(models, options) {
  533. var i, l, index, model;
  534. options || (options = {});
  535. models = _.isArray(models) ? models.slice() : [models];
  536. for (i = 0, l = models.length; i < l; i++) {
  537. model = this.getByCid(models[i]) || this.get(models[i]);
  538. if (!model) continue;
  539. delete this._byId[model.id];
  540. delete this._byCid[model.cid];
  541. index = this.indexOf(model);
  542. this.models.splice(index, 1);
  543. this.length--;
  544. if (!options.silent) {
  545. options.index = index;
  546. model.trigger('remove', model, this, options);
  547. }
  548. this._removeReference(model);
  549. }
  550. return this;
  551. },
  552. // Add a model to the end of the collection.
  553. push: function(model, options) {
  554. model = this._prepareModel(model, options);
  555. this.add(model, options);
  556. return model;
  557. },
  558. // Remove a model from the end of the collection.
  559. pop: function(options) {
  560. var model = this.at(this.length - 1);
  561. this.remove(model, options);
  562. return model;
  563. },
  564. // Add a model to the beginning of the collection.
  565. unshift: function(model, options) {
  566. model = this._prepareModel(model, options);
  567. this.add(model, _.extend({at: 0}, options));
  568. return model;
  569. },
  570. // Remove a model from the beginning of the collection.
  571. shift: function(options) {
  572. var model = this.at(0);
  573. this.remove(model, options);
  574. return model;
  575. },
  576. // Slice out a sub-array of models from the collection.
  577. slice: function(begin, end) {
  578. return this.models.slice(begin, end);
  579. },
  580. // Get a model from the set by id.
  581. get: function(id) {
  582. if (id == null) return void 0;
  583. return this._byId[id.id != null ? id.id : id];
  584. },
  585. // Get a model from the set by client id.
  586. getByCid: function(cid) {
  587. return cid && this._byCid[cid.cid || cid];
  588. },
  589. // Get the model at the given index.
  590. at: function(index) {
  591. return this.models[index];
  592. },
  593. // Return models with matching attributes. Useful for simple cases of `filter`.
  594. where: function(attrs) {
  595. if (_.isEmpty(attrs)) return [];
  596. return this.filter(function(model) {
  597. for (var key in attrs) {
  598. if (attrs[key] !== model.get(key)) return false;
  599. }
  600. return true;
  601. });
  602. },
  603. // Force the collection to re-sort itself. You don't need to call this under
  604. // normal circumstances, as the set will maintain sort order as each item
  605. // is added.
  606. sort: function(options) {
  607. if (!this.comparator) {
  608. throw new Error('Cannot sort a set without a comparator');
  609. }
  610. if (_.isString(this.comparator) || this.comparator.length === 1) {
  611. this.models = this.sortBy(this.comparator, this);
  612. } else {
  613. this.models.sort(_.bind(this.comparator, this));
  614. }
  615. if (!options || !options.silent) this.trigger('reset', this, options);
  616. return this;
  617. },
  618. // Pluck an attribute from each model in the collection.
  619. pluck: function(attr) {
  620. return _.invoke(this.models, 'get', attr);
  621. },
  622. // When you have more items than you want to add or remove individually,
  623. // you can reset the entire set with a new list of models, without firing
  624. // any `add` or `remove` events. Fires `reset` when finished.
  625. reset: function(models, options) {
  626. for (var i = 0, l = this.models.length; i < l; i++) {
  627. this._removeReference(this.models[i]);
  628. }
  629. this._reset();
  630. if (models) this.add(models, _.extend({silent: true}, options));
  631. if (!options || !options.silent) this.trigger('reset', this, options);
  632. return this;
  633. },
  634. // Fetch the default set of models for this collection, resetting the
  635. // collection when they arrive. If `add: true` is passed, appends the
  636. // models to the collection instead of resetting.
  637. fetch: function(options) {
  638. options = options ? _.clone(options) : {};
  639. if (options.parse === void 0) options.parse = true;
  640. var collection = this;
  641. var success = options.success;
  642. options.success = function(resp, status, xhr) {
  643. collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
  644. if (success) success(collection, resp, options);
  645. };
  646. return this.sync('read', this, options);
  647. },
  648. // Create a new instance of a model in this collection. Add the model to the
  649. // collection immediately, unless `wait: true` is passed, in which case we
  650. // wait for the server to agree.
  651. create: function(model, options) {
  652. var collection = this;
  653. options = options ? _.clone(options) : {};
  654. model = this._prepareModel(model, options);
  655. if (!model) return false;
  656. if (!options.wait) collection.add(model, options);
  657. var success = options.success;
  658. options.success = function(model, resp, options) {
  659. if (options.wait) collection.add(model, options);
  660. if (success) success(model, resp, options);
  661. };
  662. model.save(null, options);
  663. return model;
  664. },
  665. // **parse** converts a response into a list of models to be added to the
  666. // collection. The default implementation is just to pass it through.
  667. parse: function(resp, xhr) {
  668. return resp;
  669. },
  670. // Create a new collection with an identical list of models as this one.
  671. clone: function() {
  672. return new this.constructor(this.models);
  673. },
  674. // Proxy to _'s chain. Can't be proxied the same way the rest of the
  675. // underscore methods are proxied because it relies on the underscore
  676. // constructor.
  677. chain: function() {
  678. return _(this.models).chain();
  679. },
  680. // Reset all internal state. Called when the collection is reset.
  681. _reset: function(options) {
  682. this.length = 0;
  683. this.models = [];
  684. this._byId = {};
  685. this._byCid = {};
  686. },
  687. // Prepare a model or hash of attributes to be added to this collection.
  688. _prepareModel: function(attrs, options) {
  689. if (attrs instanceof Model) {
  690. if (!attrs.collection) attrs.collection = this;
  691. return attrs;
  692. }
  693. options || (options = {});
  694. options.collection = this;
  695. var model = new this.model(attrs, options);
  696. if (!model._validate(model.attributes, options)) return false;
  697. return model;
  698. },
  699. // Internal method to remove a model's ties to a collection.
  700. _removeReference: function(model) {
  701. if (this === model.collection) delete model.collection;
  702. model.off('all', this._onModelEvent, this);
  703. },
  704. // Internal method called every time a model in the set fires an event.
  705. // Sets need to update their indexes when models change ids. All other
  706. // events simply proxy through. "add" and "remove" events that originate
  707. // in other collections are ignored.
  708. _onModelEvent: function(event, model, collection, options) {
  709. if ((event === 'add' || event === 'remove') && collection !== this) return;
  710. if (event === 'destroy') this.remove(model, options);
  711. if (model && event === 'change:' + model.idAttribute) {
  712. delete this._byId[model.previous(model.idAttribute)];
  713. if (model.id != null) this._byId[model.id] = model;
  714. }
  715. this.trigger.apply(this, arguments);
  716. }
  717. });
  718. // Underscore methods that we want to implement on the Collection.
  719. var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
  720. 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
  721. 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
  722. 'max', 'min', 'sortedIndex', 'toArray', 'size', 'first', 'head', 'take',
  723. 'initial', 'rest', 'tail', 'last', 'without', 'indexOf', 'shuffle',
  724. 'lastIndexOf', 'isEmpty'];
  725. // Mix in each Underscore method as a proxy to `Collection#models`.
  726. _.each(methods, function(method) {
  727. Collection.prototype[method] = function() {
  728. var args = slice.call(arguments);
  729. args.unshift(this.models);
  730. return _[method].apply(_, args);
  731. };
  732. });
  733. // Underscore methods that take a property name as an argument.
  734. var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
  735. // Use attributes instead of properties.
  736. _.each(attributeMethods, function(method) {
  737. Collection.prototype[method] = function(value, context) {
  738. var iterator = _.isFunction(value) ? value : function(model) {
  739. return model.get(value);
  740. };
  741. return _[method](this.models, iterator, context);
  742. };
  743. });
  744. // Backbone.Router
  745. // -------------------
  746. // Routers map faux-URLs to actions, and fire events when routes are
  747. // matched. Creating a new one sets its `routes` hash, if not set statically.
  748. var Router = Backbone.Router = function(options) {
  749. options || (options = {});
  750. if (options.routes) this.routes = options.routes;
  751. this._bindRoutes();
  752. this.initialize.apply(this, arguments);
  753. };
  754. // Cached regular expressions for matching named param parts and splatted
  755. // parts of route strings.
  756. var namedParam = /:\w+/g;
  757. var splatParam = /\*\w+/g;
  758. var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
  759. // Set up all inheritable **Backbone.Router** properties and methods.
  760. _.extend(Router.prototype, Events, {
  761. // Initialize is an empty function by default. Override it with your own
  762. // initialization logic.
  763. initialize: function(){},
  764. // Manually bind a single named route to a callback. For example:
  765. //
  766. // this.route('search/:query/p:num', 'search', function(query, num) {
  767. // ...
  768. // });
  769. //
  770. route: function(route, name, callback) {
  771. if (!_.isRegExp(route)) route = this._routeToRegExp(route);
  772. if (!callback) callback = this[name];
  773. Backbone.history.route(route, _.bind(function(fragment) {
  774. var args = this._extractParameters(route, fragment);
  775. callback && callback.apply(this, args);
  776. this.trigger.apply(this, ['route:' + name].concat(args));
  777. Backbone.history.trigger('route', this, name, args);
  778. }, this));
  779. return this;
  780. },
  781. // Simple proxy to `Backbone.history` to save a fragment into the history.
  782. navigate: function(fragment, options) {
  783. Backbone.history.navigate(fragment, options);
  784. return this;
  785. },
  786. // Bind all defined routes to `Backbone.history`. We have to reverse the
  787. // order of the routes here to support behavior where the most general
  788. // routes can be defined at the bottom of the route map.
  789. _bindRoutes: function() {
  790. if (!this.routes) return;
  791. var routes = [];
  792. for (var route in this.routes) {
  793. routes.unshift([route, this.routes[route]]);
  794. }
  795. for (var i = 0, l = routes.length; i < l; i++) {
  796. this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
  797. }
  798. },
  799. // Convert a route string into a regular expression, suitable for matching
  800. // against the current location hash.
  801. _routeToRegExp: function(route) {
  802. route = route.replace(escapeRegExp, '\\$&')
  803. .replace(namedParam, '([^\/]+)')
  804. .replace(splatParam, '(.*?)');
  805. return new RegExp('^' + route + '$');
  806. },
  807. // Given a route, and a URL fragment that it matches, return the array of
  808. // extracted parameters.
  809. _extractParameters: function(route, fragment) {
  810. return route.exec(fragment).slice(1);
  811. }
  812. });
  813. // Backbone.History
  814. // ----------------
  815. // Handles cross-browser history management, based on URL fragments. If the
  816. // browser does not support `onhashchange`, falls back to polling.
  817. var History = Backbone.History = function() {
  818. this.handlers = [];
  819. _.bindAll(this, 'checkUrl');
  820. // #1653 - Ensure that `History` can be used outside of the browser.
  821. if (typeof window !== 'undefined') {
  822. this.location = window.location;
  823. this.history = window.history;
  824. }
  825. };
  826. // Cached regex for cleaning leading hashes and slashes.
  827. var routeStripper = /^[#\/]/;
  828. // Cached regex for stripping leading and trailing slashes.
  829. var rootStripper = /^\/+|\/+$/g;
  830. // Cached regex for detecting MSIE.
  831. var isExplorer = /msie [\w.]+/;
  832. // Cached regex for removing a trailing slash.
  833. var trailingSlash = /\/$/;
  834. // Has the history handling already been started?
  835. History.started = false;
  836. // Set up all inheritable **Backbone.History** properties and methods.
  837. _.extend(History.prototype, Events, {
  838. // The default interval to poll for hash changes, if necessary, is
  839. // twenty times a second.
  840. interval: 50,
  841. // Gets the true hash value. Cannot use location.hash directly due to bug
  842. // in Firefox where location.hash will always be decoded.
  843. getHash: function(window) {
  844. var match = (window || this).location.href.match(/#(.*)$/);
  845. return match ? match[1] : '';
  846. },
  847. // Get the cross-browser normalized URL fragment, either from the URL,
  848. // the hash, or the override.
  849. getFragment: function(fragment, forcePushState) {
  850. if (fragment == null) {
  851. if (this._hasPushState || !this._wantsHashChange || forcePushState) {
  852. fragment = this.location.pathname;
  853. var root = this.root.replace(trailingSlash, '');
  854. if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
  855. } else {
  856. fragment = this.getHash();
  857. }
  858. }
  859. return decodeURIComponent(fragment.replace(routeStripper, ''));
  860. },
  861. // Start the hash change handling, returning `true` if the current URL matches
  862. // an existing route, and `false` otherwise.
  863. start: function(options) {
  864. if (History.started) throw new Error("Backbone.history has already been started");
  865. History.started = true;
  866. // Figure out the initial configuration. Do we need an iframe?
  867. // Is pushState desired ... is it available?
  868. this.options = _.extend({}, {root: '/'}, this.options, options);
  869. this.root = this.options.root;
  870. this._wantsHashChange = this.options.hashChange !== false;
  871. this._wantsPushState = !!this.options.pushState;
  872. this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
  873. var fragment = this.getFragment();
  874. var docMode = document.documentMode;
  875. var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
  876. // Normalize root to always include a leading and trailing slash.
  877. this.root = ('/' + this.root + '/').replace(rootStripper, '/');
  878. if (oldIE && this._wantsHashChange) {
  879. this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
  880. this.navigate(fragment);
  881. }
  882. // Depending on whether we're using pushState or hashes, and whether
  883. // 'onhashchange' is supported, determine how we check the URL state.
  884. if (this._hasPushState) {
  885. Backbone.$(window).bind('popstate', this.checkUrl);
  886. } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
  887. Backbone.$(window).bind('hashchange', this.checkUrl);
  888. } else if (this._wantsHashChange) {
  889. this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
  890. }
  891. // Determine if we need to change the base url, for a pushState link
  892. // opened by a non-pushState browser.
  893. this.fragment = fragment;
  894. var loc = this.location;
  895. var atRoot = (loc.pathname.replace(/[^/]$/, '$&/') === this.root) && !loc.search;
  896. // If we've started off with a route from a `pushState`-enabled browser,
  897. // but we're currently in a browser that doesn't support it...
  898. if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
  899. this.fragment = this.getFragment(null, true);
  900. this.location.replace(this.root + this.location.search + '#' + this.fragment);
  901. // Return immediately as browser will do redirect to new url
  902. return true;
  903. // Or if we've started out with a hash-based route, but we're currently
  904. // in a browser where it could be `pushState`-based instead...
  905. } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
  906. this.fragment = this.getHash().replace(routeStripper, '');
  907. this.history.replaceState({}, document.title, this.root + this.fragment);
  908. }
  909. if (!this.options.silent) return this.loadUrl();
  910. },
  911. // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
  912. // but possibly useful for unit testing Routers.
  913. stop: function() {
  914. Backbone.$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
  915. clearInterval(this._checkUrlInterval);
  916. History.started = false;
  917. },
  918. // Add a route to be tested when the fragment changes. Routes added later
  919. // may override previous routes.
  920. route: function(route, callback) {
  921. this.handlers.unshift({route: route, callback: callback});
  922. },
  923. // Checks the current URL to see if it has changed, and if it has,
  924. // calls `loadUrl`, normalizing across the hidden iframe.
  925. checkUrl: function(e) {
  926. var current = this.getFragment();
  927. if (current === this.fragment && this.iframe) {
  928. current = this.getFragment(this.getHash(this.iframe));
  929. }
  930. if (current === this.fragment) return false;
  931. if (this.iframe) this.navigate(current);
  932. this.loadUrl() || this.loadUrl(this.getHash());
  933. },
  934. // Attempt to load the current URL fragment. If a route succeeds with a
  935. // match, returns `true`. If no defined routes matches the fragment,
  936. // returns `false`.
  937. loadUrl: function(fragmentOverride) {
  938. var fragment = this.fragment = this.getFragment(fragmentOverride);
  939. var matched = _.any(this.handlers, function(handler) {
  940. if (handler.route.test(fragment)) {
  941. handler.callback(fragment);
  942. return true;
  943. }
  944. });
  945. return matched;
  946. },
  947. // Save a fragment into the hash history, or replace the URL state if the
  948. // 'replace' option is passed. You are responsible for properly URL-encoding
  949. // the fragment in advance.
  950. //
  951. // The options object can contain `trigger: true` if you wish to have the
  952. // route callback be fired (not usually desirable), or `replace: true`, if
  953. // you wish to modify the current URL without adding an entry to the history.
  954. navigate: function(fragment, options) {
  955. if (!History.started) return false;
  956. if (!options || options === true) options = {trigger: options};
  957. fragment = this.getFragment(fragment || '');
  958. if (this.fragment === fragment) return;
  959. this.fragment = fragment;
  960. var url = this.root + fragment;
  961. // If pushState is available, we use it to set the fragment as a real URL.
  962. if (this._hasPushState) {
  963. this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
  964. // If hash changes haven't been explicitly disabled, update the hash
  965. // fragment to store history.
  966. } else if (this._wantsHashChange) {
  967. this._updateHash(this.location, fragment, options.replace);
  968. if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
  969. // Opening and closing the iframe tricks IE7 and earlier to push a
  970. // history entry on hash-tag change. When replace is true, we don't
  971. // want this.
  972. if(!options.replace) this.iframe.document.open().close();
  973. this._updateHash(this.iframe.location, fragment, options.replace);
  974. }
  975. // If you've told us that you explicitly don't want fallback hashchange-
  976. // based history, then `navigate` becomes a page refresh.
  977. } else {
  978. return this.location.assign(url);
  979. }
  980. if (options.trigger) this.loadUrl(fragment);
  981. },
  982. // Update the hash location, either replacing the current entry, or adding
  983. // a new one to the browser history.
  984. _updateHash: function(location, fragment, replace) {
  985. if (replace) {
  986. var href = location.href.replace(/(javascript:|#).*$/, '');
  987. location.replace(href + '#' + fragment);
  988. } else {
  989. // #1649 - Some browsers require that `hash` contains a leading #.
  990. location.hash = '#' + fragment;
  991. }
  992. }
  993. });
  994. // Create the default Backbone.history.
  995. Backbone.history = new History;
  996. // Backbone.View
  997. // -------------
  998. // Creating a Backbone.View creates its initial element outside of the DOM,
  999. // if an existing element is not provided...
  1000. var View = Backbone.View = function(options) {
  1001. this.cid = _.uniqueId('view');
  1002. this._configure(options || {});
  1003. this._ensureElement();
  1004. this.initialize.apply(this, arguments);
  1005. this.delegateEvents();
  1006. };
  1007. // Cached regex to split keys for `delegate`.
  1008. var delegateEventSplitter = /^(\S+)\s*(.*)$/;
  1009. // List of view options to be merged as properties.
  1010. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
  1011. // Set up all inheritable **Backbone.View** properties and methods.
  1012. _.extend(View.prototype, Events, {
  1013. // The default `tagName` of a View's element is `"div"`.
  1014. tagName: 'div',
  1015. // jQuery delegate for element lookup, scoped to DOM elements within the
  1016. // current view. This should be prefered to global lookups where possible.
  1017. $: function(selector) {
  1018. return this.$el.find(selector);
  1019. },
  1020. // Initialize is an empty function by default. Override it with your own
  1021. // initialization logic.
  1022. initialize: function(){},
  1023. // **render** is the core function that your view should override, in order
  1024. // to populate its element (`this.el`), with the appropriate HTML. The
  1025. // convention is for **render** to always return `this`.
  1026. render: function() {
  1027. return this;
  1028. },
  1029. // Clean up references to this view in order to prevent latent effects and
  1030. // memory leaks.
  1031. dispose: function() {
  1032. this.undelegateEvents();
  1033. if (this.model) this.model.off(null, null, this);
  1034. if (this.collection) this.collection.off(null, null, this);
  1035. return this;
  1036. },
  1037. // Remove this view from the DOM. Note that the view isn't present in the
  1038. // DOM by default, so calling this method may be a no-op.
  1039. remove: function() {
  1040. this.dispose();
  1041. this.$el.remove();
  1042. return this;
  1043. },
  1044. // For small amounts of DOM Elements, where a full-blown template isn't
  1045. // needed, use **make** to manufacture elements, one at a time.
  1046. //
  1047. // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
  1048. //
  1049. make: function(tagName, attributes, content) {
  1050. var el = document.createElement(tagName);
  1051. if (attributes) Backbone.$(el).attr(attributes);
  1052. if (content != null) Backbone.$(el).html(content);
  1053. return el;
  1054. },
  1055. // Change the view's element (`this.el` property), including event
  1056. // re-delegation.
  1057. setElement: function(element, delegate) {
  1058. if (this.$el) this.undelegateEvents();
  1059. this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
  1060. this.el = this.$el[0];
  1061. if (delegate !== false) this.delegateEvents();
  1062. return this;
  1063. },
  1064. // Set callbacks, where `this.events` is a hash of
  1065. //
  1066. // *{"event selector": "callback"}*
  1067. //
  1068. // {
  1069. // 'mousedown .title': 'edit',
  1070. // 'click .button': 'save'
  1071. // 'click .open': function(e) { ... }
  1072. // }
  1073. //
  1074. // pairs. Callbacks will be bound to the view, with `this` set properly.
  1075. // Uses event delegation for efficiency.
  1076. // Omitting the selector binds the event to `this.el`.
  1077. // This only works for delegate-able events: not `focus`, `blur`, and
  1078. // not `change`, `submit`, and `reset` in Internet Explorer.
  1079. delegateEvents: function(events) {
  1080. if (!(events || (events = _.result(this, 'events')))) return;
  1081. this.undelegateEvents();
  1082. for (var key in events) {
  1083. var method = events[key];
  1084. if (!_.isFunction(method)) method = this[events[key]];
  1085. if (!method) throw new Error('Method "' + events[key] + '" does not exist');
  1086. var match = key.match(delegateEventSplitter);
  1087. var eventName = match[1], selector = match[2];
  1088. method = _.bind(method, this);
  1089. eventName += '.delegateEvents' + this.cid;
  1090. if (selector === '') {
  1091. this.$el.bind(eventName, method);
  1092. } else {
  1093. this.$el.delegate(selector, eventName, method);
  1094. }
  1095. }
  1096. },
  1097. // Clears all callbacks previously bound to the view with `delegateEvents`.
  1098. // You usually don't need to use this, but may wish to if you have multiple
  1099. // Backbone views attached to the same DOM element.
  1100. undelegateEvents: function() {
  1101. this.$el.unbind('.delegateEvents' + this.cid);
  1102. },
  1103. // Performs the initial configuration of a View with a set of options.
  1104. // Keys with special meaning *(model, collection, id, className)*, are
  1105. // attached directly to the view.
  1106. _configure: function(options) {
  1107. if (this.options) options = _.extend({}, this.options, options);
  1108. for (var i = 0, l = viewOptions.length; i < l; i++) {
  1109. var attr = viewOptions[i];
  1110. if (options[attr]) this[attr] = options[attr];
  1111. }
  1112. this.options = options;
  1113. },
  1114. // Ensure that the View has a DOM element to render into.
  1115. // If `this.el` is a string, pass it through `$()`, take the first
  1116. // matching element, and re-assign it to `el`. Otherwise, create
  1117. // an element from the `id`, `className` and `tagName` properties.
  1118. _ensureElement: function() {
  1119. if (!this.el) {
  1120. var attrs = _.extend({}, _.result(this, 'attributes'));
  1121. if (this.id) attrs.id = _.result(this, 'id');
  1122. if (this.className) attrs['class'] = _.result(this, 'className');
  1123. this.setElement(this.make(_.result(this, 'tagName'), attrs), false);
  1124. } else {
  1125. this.setElement(this.el, false);
  1126. }
  1127. }
  1128. });
  1129. // Backbone.sync
  1130. // -------------
  1131. // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
  1132. var methodMap = {
  1133. 'create': 'POST',
  1134. 'update': 'PUT',
  1135. 'delete': 'DELETE',
  1136. 'read': 'GET'
  1137. };
  1138. // Override this function to change the manner in which Backbone persists
  1139. // models to the server. You will be passed the type of request, and the
  1140. // model in question. By default, makes a RESTful Ajax request
  1141. // to the model's `url()`. Some possible customizations could be:
  1142. //
  1143. // * Use `setTimeout` to batch rapid-fire updates into a single request.
  1144. // * Send up the models as XML instead of JSON.
  1145. // * Persist models via WebSockets instead of Ajax.
  1146. //
  1147. // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
  1148. // as `POST`, with a `_method` parameter containing the true HTTP method,
  1149. // as well as all requests with the body as `application/x-www-form-urlencoded`
  1150. // instead of `application/json` with the model in a param named `model`.
  1151. // Useful when interfacing with server-side languages like **PHP** that make
  1152. // it difficult to read the body of `PUT` requests.
  1153. Backbone.sync = function(method, model, options) {
  1154. var type = methodMap[method];
  1155. // Default …

Large files files are truncated, but you can click here to view the full file