PageRenderTime 64ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/Raven.Client.MvcIntegration/js/vendor/backbone.js

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