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

/support/backbone.js

https://github.com/akshayrawat/DirectoryOfMPs
JavaScript | 685 lines | 433 code | 80 blank | 172 comment | 125 complexity | ec898b443d80a49b580eaaeada2b62ab MD5 | raw file
  1. // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
  2. // Backbone may be freely distributed under the MIT license.
  3. // For all details and documentation:
  4. // http://documentcloud.github.com/backbone
  5. (function(){
  6. // Initial Setup
  7. // -------------
  8. // The top-level namespace. All public Backbone classes and modules will
  9. // be attached to this. Exported for both CommonJS and the browser.
  10. var Backbone;
  11. if (typeof exports !== 'undefined') {
  12. Backbone = exports;
  13. } else {
  14. Backbone = this.Backbone = {};
  15. }
  16. // Current version of the library. Keep in sync with `package.json`.
  17. Backbone.VERSION = '0.1.2';
  18. // Require Underscore, if we're on the server, and it's not already present.
  19. var _ = this._;
  20. if (!_ && (typeof require !== 'undefined')) _ = require("underscore")._;
  21. // For Backbone's purposes, jQuery owns the `$` variable.
  22. var $ = this.jQuery;
  23. // Turn on `emulateHttp` to fake `"PUT"` and `"DELETE"` requests via
  24. // the `_method` parameter.
  25. Backbone.emulateHttp = false;
  26. // Backbone.Events
  27. // -----------------
  28. // A module that can be mixed in to *any object* in order to provide it with
  29. // custom events. You may `bind` or `unbind` a callback function to an event;
  30. // `trigger`-ing an event fires all callbacks in succession.
  31. //
  32. // var object = {};
  33. // _.extend(object, Backbone.Events);
  34. // object.bind('expand', function(){ alert('expanded'); });
  35. // object.trigger('expand');
  36. //
  37. Backbone.Events = {
  38. // Bind an event, specified by a string name, `ev`, to a `callback` function.
  39. // Passing `"all"` will bind the callback to all events fired.
  40. bind : function(ev, callback) {
  41. var calls = this._callbacks || (this._callbacks = {});
  42. var list = this._callbacks[ev] || (this._callbacks[ev] = []);
  43. list.push(callback);
  44. return this;
  45. },
  46. // Remove one or many callbacks. If `callback` is null, removes all
  47. // callbacks for the event. If `ev` is null, removes all bound callbacks
  48. // for all events.
  49. unbind : function(ev, callback) {
  50. var calls;
  51. if (!ev) {
  52. this._callbacks = {};
  53. } else if (calls = this._callbacks) {
  54. if (!callback) {
  55. calls[ev] = [];
  56. } else {
  57. var list = calls[ev];
  58. if (!list) return this;
  59. for (var i = 0, l = list.length; i < l; i++) {
  60. if (callback === list[i]) {
  61. list.splice(i, 1);
  62. break;
  63. }
  64. }
  65. }
  66. }
  67. return this;
  68. },
  69. // Trigger an event, firing all bound callbacks. Callbacks are passed the
  70. // same arguments as `trigger` is, apart from the event name.
  71. // Listening for `"all"` passes the true event name as the first argument.
  72. trigger : function(ev) {
  73. var list, calls, i, l;
  74. if (!(calls = this._callbacks)) return this;
  75. if (list = calls[ev]) {
  76. for (i = 0, l = list.length; i < l; i++) {
  77. list[i].apply(this, Array.prototype.slice.call(arguments, 1));
  78. }
  79. }
  80. if (list = calls['all']) {
  81. for (i = 0, l = list.length; i < l; i++) {
  82. list[i].apply(this, arguments);
  83. }
  84. }
  85. return this;
  86. }
  87. };
  88. // Backbone.Model
  89. // --------------
  90. // Create a new model, with defined attributes. A client id (`cid`)
  91. // is automatically generated and assigned for you.
  92. Backbone.Model = function(attributes) {
  93. this.attributes = {};
  94. this.cid = _.uniqueId('c');
  95. this.set(attributes || {}, {silent : true});
  96. this._previousAttributes = _.clone(this.attributes);
  97. if (this.initialize) this.initialize(attributes);
  98. };
  99. // Attach all inheritable methods to the Model prototype.
  100. _.extend(Backbone.Model.prototype, Backbone.Events, {
  101. // A snapshot of the model's previous attributes, taken immediately
  102. // after the last `"change"` event was fired.
  103. _previousAttributes : null,
  104. // Has the item been changed since the last `"change"` event?
  105. _changed : false,
  106. // Return a copy of the model's `attributes` object.
  107. toJSON : function() {
  108. return _.clone(this.attributes);
  109. },
  110. // Get the value of an attribute.
  111. get : function(attr) {
  112. return this.attributes[attr];
  113. },
  114. // Set a hash of model attributes on the object, firing `"change"` unless you
  115. // choose to silence it.
  116. set : function(attrs, options) {
  117. // Extract attributes and options.
  118. options || (options = {});
  119. if (!attrs) return this;
  120. if (attrs.attributes) attrs = attrs.attributes;
  121. var now = this.attributes;
  122. // Run validation if `validate` is defined. If a specific `error` callback
  123. // has been passed, call that instead of firing the general `"error"` event.
  124. if (this.validate) {
  125. var error = this.validate(attrs);
  126. if (error) {
  127. if (options.error) {
  128. options.error(this, error);
  129. } else {
  130. this.trigger('error', this, error);
  131. }
  132. return false;
  133. }
  134. }
  135. // Check for changes of `id`.
  136. if ('id' in attrs) this.id = attrs.id;
  137. // Update attributes.
  138. for (var attr in attrs) {
  139. var val = attrs[attr];
  140. if (val === '') val = null;
  141. if (!_.isEqual(now[attr], val)) {
  142. now[attr] = val;
  143. if (!options.silent) {
  144. this._changed = true;
  145. this.trigger('change:' + attr, this, val);
  146. }
  147. }
  148. }
  149. // Fire the `"change"` event, if the model has been changed.
  150. if (!options.silent && this._changed) this.change();
  151. return this;
  152. },
  153. // Remove an attribute from the model, firing `"change"` unless you choose to
  154. // silence it.
  155. unset : function(attr, options) {
  156. options || (options = {});
  157. var value = this.attributes[attr];
  158. delete this.attributes[attr];
  159. if (!options.silent) {
  160. this._changed = true;
  161. this.trigger('change:' + attr, this);
  162. this.change();
  163. }
  164. return value;
  165. },
  166. // Fetch the model from the server. If the server's representation of the
  167. // model differs from its current attributes, they will be overriden,
  168. // triggering a `"change"` event.
  169. fetch : function(options) {
  170. options || (options = {});
  171. var model = this;
  172. var success = function(resp) {
  173. if (!model.set(resp.model, options)) return false;
  174. if (options.success) options.success(model, resp);
  175. };
  176. var error = options.error && _.bind(options.error, null, model);
  177. Backbone.sync('read', this, success, error);
  178. return this;
  179. },
  180. // Set a hash of model attributes, and sync the model to the server.
  181. // If the server returns an attributes hash that differs, the model's
  182. // state will be `set` again.
  183. save : function(attrs, options) {
  184. attrs || (attrs = {});
  185. options || (options = {});
  186. if (!this.set(attrs, options)) return false;
  187. var model = this;
  188. var success = function(resp) {
  189. if (!model.set(resp.model, options)) return false;
  190. if (options.success) options.success(model, resp);
  191. };
  192. var error = options.error && _.bind(options.error, null, model);
  193. var method = this.isNew() ? 'create' : 'update';
  194. Backbone.sync(method, this, success, error);
  195. return this;
  196. },
  197. // Destroy this model on the server. Upon success, the model is removed
  198. // from its collection, if it has one.
  199. destroy : function(options) {
  200. options || (options = {});
  201. var model = this;
  202. var success = function(resp) {
  203. if (model.collection) model.collection.remove(model);
  204. if (options.success) options.success(model, resp);
  205. };
  206. var error = options.error && _.bind(options.error, null, model);
  207. Backbone.sync('delete', this, success, error);
  208. return this;
  209. },
  210. // Default URL for the model's representation on the server -- if you're
  211. // using Backbone's restful methods, override this to change the endpoint
  212. // that will be called.
  213. url : function() {
  214. var base = getUrl(this.collection);
  215. if (this.isNew()) return base;
  216. return base + '/' + this.id;
  217. },
  218. // Create a new model with identical attributes to this one.
  219. clone : function() {
  220. return new this.constructor(this);
  221. },
  222. // A model is new if it has never been saved to the server, and has a negative
  223. // ID.
  224. isNew : function() {
  225. return !this.id;
  226. },
  227. // Call this method to fire manually fire a `change` event for this model.
  228. // Calling this will cause all objects observing the model to update.
  229. change : function() {
  230. this.trigger('change', this);
  231. this._previousAttributes = _.clone(this.attributes);
  232. this._changed = false;
  233. },
  234. // Determine if the model has changed since the last `"change"` event.
  235. // If you specify an attribute name, determine if that attribute has changed.
  236. hasChanged : function(attr) {
  237. if (attr) return this._previousAttributes[attr] != this.attributes[attr];
  238. return this._changed;
  239. },
  240. // Return an object containing all the attributes that have changed, or false
  241. // if there are no changed attributes. Useful for determining what parts of a
  242. // view need to be updated and/or what attributes need to be persisted to
  243. // the server.
  244. changedAttributes : function(now) {
  245. now || (now = this.attributes);
  246. var old = this._previousAttributes;
  247. var changed = false;
  248. for (var attr in now) {
  249. if (!_.isEqual(old[attr], now[attr])) {
  250. changed = changed || {};
  251. changed[attr] = now[attr];
  252. }
  253. }
  254. return changed;
  255. },
  256. // Get the previous value of an attribute, recorded at the time the last
  257. // `"change"` event was fired.
  258. previous : function(attr) {
  259. if (!attr || !this._previousAttributes) return null;
  260. return this._previousAttributes[attr];
  261. },
  262. // Get all of the attributes of the model at the time of the previous
  263. // `"change"` event.
  264. previousAttributes : function() {
  265. return _.clone(this._previousAttributes);
  266. }
  267. });
  268. // Backbone.Collection
  269. // -------------------
  270. // Provides a standard collection class for our sets of models, ordered
  271. // or unordered. If a `comparator` is specified, the Collection will maintain
  272. // its models in sort order, as they're added and removed.
  273. Backbone.Collection = function(models, options) {
  274. options || (options = {});
  275. if (options.comparator) {
  276. this.comparator = options.comparator;
  277. delete options.comparator;
  278. }
  279. this._boundOnModelEvent = _.bind(this._onModelEvent, this);
  280. this._reset();
  281. if (models) this.refresh(models, {silent: true});
  282. if (this.initialize) this.initialize(models, options);
  283. };
  284. // Define the Collection's inheritable methods.
  285. _.extend(Backbone.Collection.prototype, Backbone.Events, {
  286. // The default model for a collection is just a **Backbone.Model**.
  287. // This should be overridden in most cases.
  288. model : Backbone.Model,
  289. // Add a model, or list of models to the set. Pass **silent** to avoid
  290. // firing the `added` event for every new model.
  291. add : function(models, options) {
  292. if (_.isArray(models)) {
  293. for (var i = 0, l = models.length; i < l; i++) {
  294. this._add(models[i], options);
  295. }
  296. } else {
  297. this._add(models, options);
  298. }
  299. return this;
  300. },
  301. // Remove a model, or a list of models from the set. Pass silent to avoid
  302. // firing the `removed` event for every model removed.
  303. remove : function(models, options) {
  304. if (_.isArray(models)) {
  305. for (var i = 0, l = models.length; i < l; i++) {
  306. this._remove(models[i], options);
  307. }
  308. } else {
  309. this._remove(models, options);
  310. }
  311. return this;
  312. },
  313. // Get a model from the set by id.
  314. get : function(id) {
  315. return id && this._byId[id.id != null ? id.id : id];
  316. },
  317. // Get a model from the set by client id.
  318. getByCid : function(cid) {
  319. return cid && this._byCid[cid.cid || cid];
  320. },
  321. // Get the model at the given index.
  322. at: function(index) {
  323. return this.models[index];
  324. },
  325. // Force the collection to re-sort itself. You don't need to call this under normal
  326. // circumstances, as the set will maintain sort order as each item is added.
  327. sort : function(options) {
  328. options || (options = {});
  329. if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
  330. this.models = this.sortBy(this.comparator);
  331. if (!options.silent) this.trigger('refresh', this);
  332. return this;
  333. },
  334. // Pluck an attribute from each model in the collection.
  335. pluck : function(attr) {
  336. return _.map(this.models, function(model){ return model.get(attr); });
  337. },
  338. // When you have more items than you want to add or remove individually,
  339. // you can refresh the entire set with a new list of models, without firing
  340. // any `added` or `removed` events. Fires `refresh` when finished.
  341. refresh : function(models, options) {
  342. models || (models = []);
  343. options || (options = {});
  344. this._reset();
  345. this.add(models, {silent: true});
  346. if (!options.silent) this.trigger('refresh', this);
  347. return this;
  348. },
  349. // Fetch the default set of models for this collection, refreshing the
  350. // collection when they arrive.
  351. fetch : function(options) {
  352. options || (options = {});
  353. var collection = this;
  354. var success = function(resp) {
  355. collection.refresh(resp.models);
  356. if (options.success) options.success(collection, resp);
  357. };
  358. var error = options.error && _.bind(options.error, null, collection);
  359. Backbone.sync('read', this, success, error);
  360. return this;
  361. },
  362. // Create a new instance of a model in this collection. After the model
  363. // has been created on the server, it will be added to the collection.
  364. create : function(model, options) {
  365. options || (options = {});
  366. if (!(model instanceof Backbone.Model)) model = new this.model(model);
  367. model.collection = this;
  368. var success = function(resp) {
  369. model.collection.add(model);
  370. if (options.success) options.success(model, resp);
  371. };
  372. return model.save(null, {success : success, error : options.error});
  373. },
  374. // Reset all internal state. Called when the collection is refreshed.
  375. _reset : function(options) {
  376. this.length = 0;
  377. this.models = [];
  378. this._byId = {};
  379. this._byCid = {};
  380. },
  381. // Internal implementation of adding a single model to the set, updating
  382. // hash indexes for `id` and `cid` lookups.
  383. _add : function(model, options) {
  384. options || (options = {});
  385. if (!(model instanceof Backbone.Model)) {
  386. model = new this.model(model);
  387. }
  388. var already = this.getByCid(model);
  389. if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
  390. this._byId[model.id] = model;
  391. this._byCid[model.cid] = model;
  392. model.collection = this;
  393. var index = this.comparator ? this.sortedIndex(model, this.comparator) : this.length;
  394. this.models.splice(index, 0, model);
  395. model.bind('all', this._boundOnModelEvent);
  396. this.length++;
  397. if (!options.silent) this.trigger('add', model);
  398. return model;
  399. },
  400. // Internal implementation of removing a single model from the set, updating
  401. // hash indexes for `id` and `cid` lookups.
  402. _remove : function(model, options) {
  403. options || (options = {});
  404. model = this.getByCid(model);
  405. if (!model) return null;
  406. delete this._byId[model.id];
  407. delete this._byCid[model.cid];
  408. delete model.collection;
  409. this.models.splice(this.indexOf(model), 1);
  410. model.unbind('all', this._boundOnModelEvent);
  411. this.length--;
  412. if (!options.silent) this.trigger('remove', model);
  413. return model;
  414. },
  415. // Internal method called every time a model in the set fires an event.
  416. // Sets need to update their indexes when models change ids.
  417. _onModelEvent : function(ev, model, error) {
  418. switch (ev) {
  419. case 'change':
  420. if (model.hasChanged('id')) {
  421. delete this._byId[model.previous('id')];
  422. this._byId[model.id] = model;
  423. }
  424. this.trigger('change', model);
  425. break;
  426. case 'error':
  427. this.trigger('error', model, error);
  428. break;
  429. }
  430. }
  431. });
  432. // Underscore methods that we want to implement on the Collection.
  433. var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
  434. 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
  435. 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
  436. 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty'];
  437. // Mix in each Underscore method as a proxy to `Collection#models`.
  438. _.each(methods, function(method) {
  439. Backbone.Collection.prototype[method] = function() {
  440. return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
  441. };
  442. });
  443. // Backbone.View
  444. // -------------
  445. // Creating a Backbone.View creates its initial element outside of the DOM,
  446. // if an existing element is not provided...
  447. Backbone.View = function(options) {
  448. this._configure(options || {});
  449. if (this.options.el) {
  450. this.el = this.options.el;
  451. } else {
  452. var attrs = {};
  453. if (this.id) attrs.id = this.id;
  454. if (this.className) attrs.className = this.className;
  455. this.el = this.make(this.tagName, attrs);
  456. }
  457. if (this.initialize) this.initialize(options);
  458. };
  459. // jQuery lookup, scoped to DOM elements within the current view.
  460. // This should be prefered to global jQuery lookups, if you're dealing with
  461. // a specific view.
  462. var jQueryDelegate = function(selector) {
  463. return $(selector, this.el);
  464. };
  465. // Cached regex to split keys for `handleEvents`.
  466. var eventSplitter = /^(\w+)\s*(.*)$/;
  467. // Set up all inheritable **Backbone.View** properties and methods.
  468. _.extend(Backbone.View.prototype, {
  469. // The default `tagName` of a View's element is `"div"`.
  470. tagName : 'div',
  471. // Attach the jQuery function as the `$` and `jQuery` properties.
  472. $ : jQueryDelegate,
  473. jQuery : jQueryDelegate,
  474. // **render** is the core function that your view should override, in order
  475. // to populate its element (`this.el`), with the appropriate HTML. The
  476. // convention is for **render** to always return `this`.
  477. render : function() {
  478. return this;
  479. },
  480. // For small amounts of DOM Elements, where a full-blown template isn't
  481. // needed, use **make** to manufacture elements, one at a time.
  482. //
  483. // var el = this.make('li', {'class': 'row'}, this.model.get('title'));
  484. //
  485. make : function(tagName, attributes, content) {
  486. var el = document.createElement(tagName);
  487. if (attributes) $(el).attr(attributes);
  488. if (content) $(el).html(content);
  489. return el;
  490. },
  491. // Set callbacks, where `this.callbacks` is a hash of
  492. //
  493. // *{"event selector": "callback"}*
  494. //
  495. // {
  496. // 'mousedown .title': 'edit',
  497. // 'click .button': 'save'
  498. // }
  499. //
  500. // pairs. Callbacks will be bound to the view, with `this` set properly.
  501. // Uses jQuery event delegation for efficiency.
  502. // Omitting the selector binds the event to `this.el`.
  503. // `"change"` events are not delegated through the view because IE does not
  504. // bubble change events at all.
  505. handleEvents : function(events) {
  506. $(this.el).unbind();
  507. if (!(events || (events = this.events))) return this;
  508. for (var key in events) {
  509. var methodName = events[key];
  510. var match = key.match(eventSplitter);
  511. var eventName = match[1], selector = match[2];
  512. var method = _.bind(this[methodName], this);
  513. if (selector === '' || eventName == 'change') {
  514. $(this.el).bind(eventName, method);
  515. } else {
  516. $(this.el).delegate(selector, eventName, method);
  517. }
  518. }
  519. return this;
  520. },
  521. // Performs the initial configuration of a View with a set of options.
  522. // Keys with special meaning *(model, collection, id, className)*, are
  523. // attached directly to the view.
  524. _configure : function(options) {
  525. if (this.options) options = _.extend({}, this.options, options);
  526. if (options.model) this.model = options.model;
  527. if (options.collection) this.collection = options.collection;
  528. if (options.id) this.id = options.id;
  529. if (options.className) this.className = options.className;
  530. if (options.tagName) this.tagName = options.tagName;
  531. this.options = options;
  532. }
  533. });
  534. // Set up inheritance for the model, collection, and view.
  535. var extend = Backbone.Model.extend = Backbone.Collection.extend = Backbone.View.extend = function (protoProps, classProps) {
  536. var child = inherits(this, protoProps, classProps);
  537. child.extend = extend;
  538. return child;
  539. };
  540. // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
  541. var methodMap = {
  542. 'create': 'POST',
  543. 'update': 'PUT',
  544. 'delete': 'DELETE',
  545. 'read' : 'GET'
  546. };
  547. // Backbone.sync
  548. // -------------
  549. // Override this function to change the manner in which Backbone persists
  550. // models to the server. You will be passed the type of request, and the
  551. // model in question. By default, uses jQuery to make a RESTful Ajax request
  552. // to the model's `url()`. Some possible customizations could be:
  553. //
  554. // * Use `setTimeout` to batch rapid-fire updates into a single request.
  555. // * Send up the models as XML instead of JSON.
  556. // * Persist models via WebSockets instead of Ajax.
  557. //
  558. // Turn on `Backbone.emulateHttp` in order to send `PUT` and `DELETE` requests
  559. // as `POST`, with an `_method` parameter containing the true HTTP method.
  560. // Useful when interfacing with server-side languages like **PHP** that make
  561. // it difficult to read the body of `PUT` requests.
  562. Backbone.sync = function(method, model, success, error) {
  563. var sendModel = method === 'create' || method === 'update';
  564. var data = sendModel ? {model : JSON.stringify(model)} : {};
  565. var type = methodMap[method];
  566. if (Backbone.emulateHttp && (type === 'PUT' || type === 'DELETE')) {
  567. data._method = type;
  568. type = 'POST';
  569. }
  570. $.ajax({
  571. url : getUrl(model),
  572. type : type,
  573. data : data,
  574. dataType : 'json',
  575. success : success,
  576. error : error
  577. });
  578. };
  579. // Helpers
  580. // -------
  581. // Helper function to correctly set up the prototype chain, for subclasses.
  582. // Similar to `goog.inherits`, but uses a hash of prototype properties and
  583. // class properties to be extended.
  584. var inherits = function(parent, protoProps, classProps) {
  585. var child;
  586. if (protoProps.hasOwnProperty('constructor')) {
  587. child = protoProps.constructor;
  588. } else {
  589. child = function(){ return parent.apply(this, arguments); };
  590. }
  591. var ctor = function(){};
  592. ctor.prototype = parent.prototype;
  593. child.prototype = new ctor();
  594. _.extend(child.prototype, protoProps);
  595. if (classProps) _.extend(child, classProps);
  596. child.prototype.constructor = child;
  597. return child;
  598. };
  599. // Helper function to get a URL from a Model or Collection as a property
  600. // or as a function.
  601. var getUrl = function(object) {
  602. if (!(object && object.url)) throw new Error("A 'url' property or function must be specified");
  603. return _.isFunction(object.url) ? object.url() : object.url;
  604. };
  605. })();