PageRenderTime 56ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/ajax_helper/media/js/backbone.js

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