PageRenderTime 56ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/Grails/backbone/web-app/js/lib/backbone.js

https://github.com/Refactr/open-source
JavaScript | 709 lines | 441 code | 85 blank | 183 comment | 122 complexity | 72ca466bce1b49364e1c96a0c61c2a66 MD5 | raw file
Possible License(s): BSD-3-Clause
  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.2.0';
  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(model.parse(resp), 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(model.parse(resp), 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. // **parse** converts a response into the hash of attributes to be `set` on
  219. // the model. The default implementation is just to pass the response along.
  220. parse : function(resp) {
  221. return resp;
  222. },
  223. // Create a new model with identical attributes to this one.
  224. clone : function() {
  225. return new this.constructor(this);
  226. },
  227. // A model is new if it has never been saved to the server, and has a negative
  228. // ID.
  229. isNew : function() {
  230. return !this.id;
  231. },
  232. // Call this method to fire manually fire a `change` event for this model.
  233. // Calling this will cause all objects observing the model to update.
  234. change : function() {
  235. this.trigger('change', this);
  236. this._previousAttributes = _.clone(this.attributes);
  237. this._changed = false;
  238. },
  239. // Determine if the model has changed since the last `"change"` event.
  240. // If you specify an attribute name, determine if that attribute has changed.
  241. hasChanged : function(attr) {
  242. if (attr) return this._previousAttributes[attr] != this.attributes[attr];
  243. return this._changed;
  244. },
  245. // Return an object containing all the attributes that have changed, or false
  246. // if there are no changed attributes. Useful for determining what parts of a
  247. // view need to be updated and/or what attributes need to be persisted to
  248. // the server.
  249. changedAttributes : function(now) {
  250. now || (now = this.attributes);
  251. var old = this._previousAttributes;
  252. var changed = false;
  253. for (var attr in now) {
  254. if (!_.isEqual(old[attr], now[attr])) {
  255. changed = changed || {};
  256. changed[attr] = now[attr];
  257. }
  258. }
  259. return changed;
  260. },
  261. // Get the previous value of an attribute, recorded at the time the last
  262. // `"change"` event was fired.
  263. previous : function(attr) {
  264. if (!attr || !this._previousAttributes) return null;
  265. return this._previousAttributes[attr];
  266. },
  267. // Get all of the attributes of the model at the time of the previous
  268. // `"change"` event.
  269. previousAttributes : function() {
  270. return _.clone(this._previousAttributes);
  271. }
  272. });
  273. // Backbone.Collection
  274. // -------------------
  275. // Provides a standard collection class for our sets of models, ordered
  276. // or unordered. If a `comparator` is specified, the Collection will maintain
  277. // its models in sort order, as they're added and removed.
  278. Backbone.Collection = function(models, options) {
  279. options || (options = {});
  280. if (options.comparator) {
  281. this.comparator = options.comparator;
  282. delete options.comparator;
  283. }
  284. this._boundOnModelEvent = _.bind(this._onModelEvent, this);
  285. this._reset();
  286. if (models) this.refresh(models, {silent: true});
  287. if (this.initialize) this.initialize(models, options);
  288. };
  289. // Define the Collection's inheritable methods.
  290. _.extend(Backbone.Collection.prototype, Backbone.Events, {
  291. // The default model for a collection is just a **Backbone.Model**.
  292. // This should be overridden in most cases.
  293. model : Backbone.Model,
  294. // The JSON representation of a Collection is an array of the
  295. // models' attributes.
  296. toJSON : function() {
  297. return this.map(function(model){ return model.toJSON(); });
  298. },
  299. // Add a model, or list of models to the set. Pass **silent** to avoid
  300. // firing the `added` event for every new model.
  301. add : function(models, options) {
  302. if (_.isArray(models)) {
  303. for (var i = 0, l = models.length; i < l; i++) {
  304. this._add(models[i], options);
  305. }
  306. } else {
  307. this._add(models, options);
  308. }
  309. return this;
  310. },
  311. // Remove a model, or a list of models from the set. Pass silent to avoid
  312. // firing the `removed` event for every model removed.
  313. remove : function(models, options) {
  314. if (_.isArray(models)) {
  315. for (var i = 0, l = models.length; i < l; i++) {
  316. this._remove(models[i], options);
  317. }
  318. } else {
  319. this._remove(models, options);
  320. }
  321. return this;
  322. },
  323. // Get a model from the set by id.
  324. get : function(id) {
  325. return id && this._byId[id.id != null ? id.id : id];
  326. },
  327. // Get a model from the set by client id.
  328. getByCid : function(cid) {
  329. return cid && this._byCid[cid.cid || cid];
  330. },
  331. // Get the model at the given index.
  332. at: function(index) {
  333. return this.models[index];
  334. },
  335. // Force the collection to re-sort itself. You don't need to call this under normal
  336. // circumstances, as the set will maintain sort order as each item is added.
  337. sort : function(options) {
  338. options || (options = {});
  339. if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
  340. this.models = this.sortBy(this.comparator);
  341. if (!options.silent) this.trigger('refresh', this);
  342. return this;
  343. },
  344. // Pluck an attribute from each model in the collection.
  345. pluck : function(attr) {
  346. return _.map(this.models, function(model){ return model.get(attr); });
  347. },
  348. // When you have more items than you want to add or remove individually,
  349. // you can refresh the entire set with a new list of models, without firing
  350. // any `added` or `removed` events. Fires `refresh` when finished.
  351. refresh : function(models, options) {
  352. models || (models = []);
  353. options || (options = {});
  354. this._reset();
  355. this.add(models, {silent: true});
  356. if (!options.silent) this.trigger('refresh', this);
  357. return this;
  358. },
  359. // Fetch the default set of models for this collection, refreshing the
  360. // collection when they arrive.
  361. fetch : function(options) {
  362. options || (options = {});
  363. var collection = this;
  364. var success = function(resp) {
  365. collection.refresh(collection.parse(resp));
  366. if (options.success) options.success(collection, resp);
  367. };
  368. var error = options.error && _.bind(options.error, null, collection);
  369. Backbone.sync('read', this, success, error);
  370. return this;
  371. },
  372. // Create a new instance of a model in this collection. After the model
  373. // has been created on the server, it will be added to the collection.
  374. create : function(model, options) {
  375. options || (options = {});
  376. if (!(model instanceof Backbone.Model)) model = new this.model(model);
  377. var coll = model.collection = this;
  378. var success = function(nextModel, resp) {
  379. coll.add(nextModel);
  380. if (options.success) options.success(nextModel, resp);
  381. };
  382. return model.save(null, {success : success, error : options.error});
  383. },
  384. // **parse** converts a response into a list of models to be added to the
  385. // collection. The default implementation is just to pass it through.
  386. parse : function(resp) {
  387. return resp;
  388. },
  389. // Proxy to _'s chain. Can't be proxied the same way the rest of the
  390. // underscore methods are proxied because it relies on the underscore
  391. // constructor.
  392. chain: function () {
  393. return _(this.models).chain();
  394. },
  395. // Reset all internal state. Called when the collection is refreshed.
  396. _reset : function(options) {
  397. this.length = 0;
  398. this.models = [];
  399. this._byId = {};
  400. this._byCid = {};
  401. },
  402. // Internal implementation of adding a single model to the set, updating
  403. // hash indexes for `id` and `cid` lookups.
  404. _add : function(model, options) {
  405. options || (options = {});
  406. if (!(model instanceof Backbone.Model)) {
  407. model = new this.model(model);
  408. }
  409. var already = this.getByCid(model);
  410. if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
  411. this._byId[model.id] = model;
  412. this._byCid[model.cid] = model;
  413. model.collection = this;
  414. var index = this.comparator ? this.sortedIndex(model, this.comparator) : this.length;
  415. this.models.splice(index, 0, model);
  416. model.bind('all', this._boundOnModelEvent);
  417. this.length++;
  418. if (!options.silent) this.trigger('add', model);
  419. return model;
  420. },
  421. // Internal implementation of removing a single model from the set, updating
  422. // hash indexes for `id` and `cid` lookups.
  423. _remove : function(model, options) {
  424. options || (options = {});
  425. model = this.getByCid(model);
  426. if (!model) return null;
  427. delete this._byId[model.id];
  428. delete this._byCid[model.cid];
  429. delete model.collection;
  430. this.models.splice(this.indexOf(model), 1);
  431. model.unbind('all', this._boundOnModelEvent);
  432. this.length--;
  433. if (!options.silent) this.trigger('remove', model);
  434. return model;
  435. },
  436. // Internal method called every time a model in the set fires an event.
  437. // Sets need to update their indexes when models change ids. All other
  438. // events simply proxy through.
  439. _onModelEvent : function(ev, model) {
  440. if (ev === 'change:id') {
  441. delete this._byId[model.previous('id')];
  442. this._byId[model.id] = model;
  443. }
  444. this.trigger.apply(this, arguments);
  445. }
  446. });
  447. // Underscore methods that we want to implement on the Collection.
  448. var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
  449. 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
  450. 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
  451. 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty'];
  452. // Mix in each Underscore method as a proxy to `Collection#models`.
  453. _.each(methods, function(method) {
  454. Backbone.Collection.prototype[method] = function() {
  455. return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
  456. };
  457. });
  458. // Backbone.View
  459. // -------------
  460. // Creating a Backbone.View creates its initial element outside of the DOM,
  461. // if an existing element is not provided...
  462. Backbone.View = function(options) {
  463. this._configure(options || {});
  464. this._ensureElement();
  465. this.delegateEvents();
  466. if (this.initialize) this.initialize(options);
  467. };
  468. // jQuery lookup, scoped to DOM elements within the current view.
  469. // This should be prefered to global jQuery lookups, if you're dealing with
  470. // a specific view.
  471. var jQueryDelegate = function(selector) {
  472. return $(selector, this.el);
  473. };
  474. // Cached regex to split keys for `delegate`.
  475. var eventSplitter = /^(\w+)\s*(.*)$/;
  476. // Set up all inheritable **Backbone.View** properties and methods.
  477. _.extend(Backbone.View.prototype, {
  478. // The default `tagName` of a View's element is `"div"`.
  479. tagName : 'div',
  480. // Attach the jQuery function as the `$` and `jQuery` properties.
  481. $ : jQueryDelegate,
  482. jQuery : jQueryDelegate,
  483. // **render** is the core function that your view should override, in order
  484. // to populate its element (`this.el`), with the appropriate HTML. The
  485. // convention is for **render** to always return `this`.
  486. render : function() {
  487. return this;
  488. },
  489. // For small amounts of DOM Elements, where a full-blown template isn't
  490. // needed, use **make** to manufacture elements, one at a time.
  491. //
  492. // var el = this.make('li', {'class': 'row'}, this.model.get('title'));
  493. //
  494. make : function(tagName, attributes, content) {
  495. var el = document.createElement(tagName);
  496. if (attributes) $(el).attr(attributes);
  497. if (content) $(el).html(content);
  498. return el;
  499. },
  500. // Set callbacks, where `this.callbacks` is a hash of
  501. //
  502. // *{"event selector": "callback"}*
  503. //
  504. // {
  505. // 'mousedown .title': 'edit',
  506. // 'click .button': 'save'
  507. // }
  508. //
  509. // pairs. Callbacks will be bound to the view, with `this` set properly.
  510. // Uses jQuery event delegation for efficiency.
  511. // Omitting the selector binds the event to `this.el`.
  512. // This only works for delegate-able events: not `focus`, `blur`, and
  513. // not `change`, `submit`, and `reset` in Internet Explorer.
  514. delegateEvents : function(events) {
  515. if (!(events || (events = this.events))) return this;
  516. $(this.el).unbind();
  517. for (var key in events) {
  518. var methodName = events[key];
  519. var match = key.match(eventSplitter);
  520. var eventName = match[1], selector = match[2];
  521. var method = _.bind(this[methodName], this);
  522. if (selector === '') {
  523. $(this.el).bind(eventName, method);
  524. } else {
  525. $(this.el).delegate(selector, eventName, method);
  526. }
  527. }
  528. return this;
  529. },
  530. // Performs the initial configuration of a View with a set of options.
  531. // Keys with special meaning *(model, collection, id, className)*, are
  532. // attached directly to the view.
  533. _configure : function(options) {
  534. if (this.options) options = _.extend({}, this.options, options);
  535. if (options.model) this.model = options.model;
  536. if (options.collection) this.collection = options.collection;
  537. if (options.el) this.el = options.el;
  538. if (options.id) this.id = options.id;
  539. if (options.className) this.className = options.className;
  540. if (options.tagName) this.tagName = options.tagName;
  541. this.options = options;
  542. },
  543. // Ensure that the View has a DOM element to render into.
  544. _ensureElement : function() {
  545. if (this.el) return;
  546. var attrs = {};
  547. if (this.id) attrs.id = this.id;
  548. if (this.className) attrs.className = this.className;
  549. this.el = this.make(this.tagName, attrs);
  550. }
  551. });
  552. // Set up inheritance for the model, collection, and view.
  553. var extend = Backbone.Model.extend = Backbone.Collection.extend = Backbone.View.extend = function (protoProps, classProps) {
  554. var child = inherits(this, protoProps, classProps);
  555. child.extend = extend;
  556. return child;
  557. };
  558. // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
  559. var methodMap = {
  560. 'create': 'POST',
  561. 'update': 'PUT',
  562. 'delete': 'DELETE',
  563. 'read' : 'GET'
  564. };
  565. // Backbone.sync
  566. // -------------
  567. // Override this function to change the manner in which Backbone persists
  568. // models to the server. You will be passed the type of request, and the
  569. // model in question. By default, uses jQuery to make a RESTful Ajax request
  570. // to the model's `url()`. Some possible customizations could be:
  571. //
  572. // * Use `setTimeout` to batch rapid-fire updates into a single request.
  573. // * Send up the models as XML instead of JSON.
  574. // * Persist models via WebSockets instead of Ajax.
  575. //
  576. // Turn on `Backbone.emulateHttp` in order to send `PUT` and `DELETE` requests
  577. // as `POST`, with an `_method` parameter containing the true HTTP method.
  578. // Useful when interfacing with server-side languages like **PHP** that make
  579. // it difficult to read the body of `PUT` requests.
  580. Backbone.sync = function(method, model, success, error) {
  581. var sendModel = method === 'create' || method === 'update';
  582. var data = sendModel ? {model : JSON.stringify(model)} : {};
  583. var type = methodMap[method];
  584. if (Backbone.emulateHttp && (type === 'PUT' || type === 'DELETE')) {
  585. data._method = type;
  586. type = 'POST';
  587. }
  588. $.ajax({
  589. url : getUrl(model),
  590. type : type,
  591. data : data,
  592. dataType : 'json',
  593. success : success,
  594. error : error
  595. });
  596. };
  597. // Helpers
  598. // -------
  599. // Helper function to correctly set up the prototype chain, for subclasses.
  600. // Similar to `goog.inherits`, but uses a hash of prototype properties and
  601. // class properties to be extended.
  602. var inherits = function(parent, protoProps, classProps) {
  603. var child;
  604. if (protoProps.hasOwnProperty('constructor')) {
  605. child = protoProps.constructor;
  606. } else {
  607. child = function(){ return parent.apply(this, arguments); };
  608. }
  609. var ctor = function(){};
  610. ctor.prototype = parent.prototype;
  611. child.prototype = new ctor();
  612. _.extend(child.prototype, protoProps);
  613. if (classProps) _.extend(child, classProps);
  614. child.prototype.constructor = child;
  615. child.__super__ = parent.prototype;
  616. return child;
  617. };
  618. // Helper function to get a URL from a Model or Collection as a property
  619. // or as a function.
  620. var getUrl = function(object) {
  621. if (!(object && object.url)) throw new Error("A 'url' property or function must be specified");
  622. return _.isFunction(object.url) ? object.url() : object.url;
  623. };
  624. })();