PageRenderTime 37ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/ajax/libs/backbone.marionette/0.7.0/backbone.marionette.js

https://gitlab.com/Blueprint-Marketing/cdnjs
JavaScript | 797 lines | 460 code | 136 blank | 201 comment | 54 complexity | aba0ee5ddc01d1a96a5640e1d20cb43b MD5 | raw file
  1. // Backbone.Marionette v0.7.0
  2. //
  3. // Copyright (C)2011 Derick Bailey, Muted Solutions, LLC
  4. // Distributed Under MIT License
  5. //
  6. // Documentation and Full License Available at:
  7. // http://github.com/derickbailey/backbone.marionette
  8. Backbone.Marionette = (function(Backbone, _, $){
  9. var Marionette = {};
  10. Marionette.version = "0.7.0";
  11. // Marionette.View
  12. // ---------------
  13. // The core view type that other Marionette views extend from.
  14. Marionette.View = Backbone.View.extend({
  15. // Get the template or template id/selector for this view
  16. // instance. You can set a `template` attribute in the view
  17. // definition or pass a `template: "whatever"` parameter in
  18. // to the constructor options. The `template` can also be
  19. // a function that returns a selector string.
  20. getTemplateSelector: function(){
  21. var template;
  22. // Get the template from `this.options.template` or
  23. // `this.template`. The `options` takes precedence.
  24. if (this.options && this.options.template){
  25. template = this.options.template;
  26. } else {
  27. template = this.template;
  28. }
  29. // check if it's a function and execute it, if it is
  30. if (_.isFunction(template)){
  31. template = template.call(this);
  32. }
  33. return template;
  34. },
  35. // Default `close` implementation, for removing a view from the
  36. // DOM and unbinding it. Regions will call this method
  37. // for you. You can specify an `onClose` method in your view to
  38. // add custom code that is called after the view is closed.
  39. close: function(){
  40. this.beforeClose && this.beforeClose();
  41. this.unbindAll();
  42. this.remove();
  43. this.onClose && this.onClose();
  44. this.trigger('close');
  45. this.unbind();
  46. }
  47. });
  48. // Item View
  49. // ---------
  50. // A single item view implementation that contains code for rendering
  51. // with underscore.js templates, serializing the view's model or collection,
  52. // and calling several methods on extended views, such as `onRender`.
  53. Marionette.ItemView = Marionette.View.extend({
  54. constructor: function(){
  55. var args = slice.call(arguments);
  56. Marionette.View.prototype.constructor.apply(this, args);
  57. _.bindAll(this, "render");
  58. this.initialEvents();
  59. },
  60. // Configured the initial events that the item view
  61. // binds to. Override this method to prevent the initial
  62. // events, or to add your own initial events.
  63. initialEvents: function(){
  64. if (this.collection){
  65. this.bindTo(this.collection, "reset", this.render, this);
  66. }
  67. },
  68. // Serialize the model or collection for the view. If a model is
  69. // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
  70. // is also called, but is used to populate an `items` array in the
  71. // resulting data. If both are found, defaults to the model.
  72. // You can override the `serializeData` method in your own view
  73. // definition, to provide custom serialization for your view's data.
  74. serializeData: function(){
  75. var data;
  76. if (this.model) { data = this.model.toJSON(); }
  77. else if (this.collection) {
  78. data = { items: this.collection.toJSON() };
  79. }
  80. return data;
  81. },
  82. // Render the view, defaulting to underscore.js templates.
  83. // You can override this in your view definition.
  84. render: function(){
  85. var that = this;
  86. var deferredRender = $.Deferred();
  87. var template = this.getTemplateSelector();
  88. var deferredData = this.serializeData();
  89. this.beforeRender && this.beforeRender();
  90. this.trigger("item:before:render", that);
  91. $.when(deferredData).then(function(data) {
  92. var asyncRender = Marionette.Renderer.render(template, data);
  93. $.when(asyncRender).then(function(html){
  94. that.$el.html(html);
  95. that.onRender && that.onRender();
  96. that.trigger("item:rendered", that);
  97. that.trigger("render", that);
  98. deferredRender.resolve();
  99. });
  100. });
  101. return deferredRender.promise();
  102. },
  103. // Override the default close event to add a few
  104. // more events that are triggered.
  105. close: function(){
  106. this.trigger('item:before:close');
  107. Marionette.View.prototype.close.apply(this, arguments);
  108. this.trigger('item:closed');
  109. }
  110. });
  111. // Collection View
  112. // ---------------
  113. // A view that iterates over a Backbone.Collection
  114. // and renders an individual ItemView for each model.
  115. Marionette.CollectionView = Marionette.View.extend({
  116. constructor: function(){
  117. Marionette.View.prototype.constructor.apply(this, arguments);
  118. _.bindAll(this, "addItemView", "render");
  119. this.initialEvents();
  120. },
  121. // Configured the initial events that the collection view
  122. // binds to. Override this method to prevent the initial
  123. // events, or to add your own initial events.
  124. initialEvents: function(){
  125. if (this.collection){
  126. this.bindTo(this.collection, "add", this.addChildView, this);
  127. this.bindTo(this.collection, "remove", this.removeItemView, this);
  128. this.bindTo(this.collection, "reset", this.render, this);
  129. }
  130. },
  131. // Handle a child item added to the collection
  132. addChildView: function(item){
  133. var ItemView = this.getItemView();
  134. return this.addItemView(item, ItemView);
  135. },
  136. // Loop through all of the items and render
  137. // each of them with the specified `itemView`.
  138. render: function(){
  139. var that = this;
  140. var deferredRender = $.Deferred();
  141. var promises = [];
  142. var ItemView = this.getItemView();
  143. this.beforeRender && this.beforeRender();
  144. this.trigger("collection:before:render", this);
  145. this.closeChildren();
  146. this.collection && this.collection.each(function(item){
  147. var promise = that.addItemView(item, ItemView);
  148. promises.push(promise);
  149. });
  150. deferredRender.done(function(){
  151. this.onRender && this.onRender();
  152. this.trigger("collection:rendered", this);
  153. });
  154. $.when(promises).then(function(){
  155. deferredRender.resolveWith(that);
  156. });
  157. return deferredRender.promise();
  158. },
  159. // Retrieve the itemView type, either from `this.options.itemView`
  160. // or from the `itemView` in the object definition. The "options"
  161. // takes precedence.
  162. getItemView: function(){
  163. var itemView = this.options.itemView || this.itemView;
  164. if (!itemView){
  165. var err = new Error("An `itemView` must be specified");
  166. err.name = "NoItemViewError";
  167. throw err;
  168. }
  169. return itemView;
  170. },
  171. // Render the child item's view and add it to the
  172. // HTML for the collection view.
  173. addItemView: function(item, ItemView){
  174. var that = this;
  175. var view = this.buildItemView(item, ItemView);
  176. this.storeChild(view);
  177. this.trigger("item:added", view);
  178. var viewRendered = view.render();
  179. $.when(viewRendered).then(function(){
  180. that.appendHtml(that, view);
  181. });
  182. return viewRendered;
  183. },
  184. // Build an `itemView` for every model in the collection.
  185. buildItemView: function(item, ItemView){
  186. var view = new ItemView({
  187. model: item
  188. });
  189. return view;
  190. },
  191. // Remove the child view and close it
  192. removeItemView: function(item){
  193. var view = this.children[item.cid];
  194. if (view){
  195. view.close();
  196. delete this.children[item.cid];
  197. }
  198. this.trigger("item:removed", view);
  199. },
  200. // Append the HTML to the collection's `el`.
  201. // Override this method to do something other
  202. // then `.append`.
  203. appendHtml: function(collectionView, itemView){
  204. collectionView.$el.append(itemView.el);
  205. },
  206. // Store references to all of the child `itemView`
  207. // instances so they can be managed and cleaned up, later.
  208. storeChild: function(view){
  209. if (!this.children){
  210. this.children = {};
  211. }
  212. this.children[view.model.cid] = view;
  213. },
  214. // Handle cleanup and other closing needs for
  215. // the collection of views.
  216. close: function(){
  217. this.trigger("collection:before:close");
  218. this.closeChildren();
  219. Marionette.View.prototype.close.apply(this, arguments);
  220. this.trigger("collection:closed");
  221. },
  222. closeChildren: function(){
  223. if (this.children){
  224. _.each(this.children, function(childView){
  225. childView.close();
  226. });
  227. }
  228. }
  229. });
  230. // Composite View
  231. // --------------
  232. // Used for rendering a branch-leaf, hierarchical structure.
  233. // Extends directly from CollectionView and also renders an
  234. // an item view as `modelView`, for the top leaf
  235. Marionette.CompositeView = Marionette.CollectionView.extend({
  236. constructor: function(options){
  237. Marionette.CollectionView.apply(this, arguments);
  238. this.itemView = this.getItemView();
  239. },
  240. // Retrieve the `itemView` to be used when rendering each of
  241. // the items in the collection. The default is to return
  242. // `this.itemView` or Marionette.CompositeView if no `itemView`
  243. // has been defined
  244. getItemView: function(){
  245. return this.itemView || this.constructor;
  246. },
  247. // Renders the model once, and the collection once. Calling
  248. // this again will tell the model's view to re-render itself
  249. // but the collection will not re-render.
  250. render: function(){
  251. var that = this;
  252. var compositeRendered = $.Deferred();
  253. var modelIsRendered = this.renderModel();
  254. $.when(modelIsRendered).then(function(html){
  255. that.$el.html(html);
  256. that.trigger("composite:model:rendered");
  257. that.trigger("render");
  258. var collectionIsRendered = that.renderCollection();
  259. $.when(collectionIsRendered).then(function(){
  260. compositeRendered.resolve();
  261. });
  262. });
  263. compositeRendered.done(function(){
  264. that.trigger("composite:rendered");
  265. });
  266. return compositeRendered.promise();
  267. },
  268. // Render the collection for the composite view
  269. renderCollection: function(){
  270. var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
  271. collectionDeferred.done(function(){
  272. this.trigger("composite:collection:rendered");
  273. });
  274. return collectionDeferred.promise();
  275. },
  276. // Render an individual model, if we have one, as
  277. // part of a composite view (branch / leaf). For example:
  278. // a treeview.
  279. renderModel: function(){
  280. var data = {};
  281. if (this.model){
  282. data = this.model.toJSON();
  283. }
  284. var template = this.getTemplateSelector();
  285. return Marionette.Renderer.render(template, data);
  286. }
  287. });
  288. // Region
  289. // ------
  290. // Manage the visual regions of your composite application. See
  291. // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
  292. Marionette.Region = function(options){
  293. this.options = options || {};
  294. _.extend(this, options);
  295. if (!this.el){
  296. var err = new Error("An 'el' must be specified");
  297. err.name = "NoElError";
  298. throw err;
  299. }
  300. };
  301. _.extend(Marionette.Region.prototype, Backbone.Events, {
  302. // Displays a backbone view instance inside of the region.
  303. // Handles calling the `render` method for you. Reads content
  304. // directly from the `el` attribute. Also calls an optional
  305. // `onShow` and `close` method on your view, just after showing
  306. // or just before closing the view, respectively.
  307. show: function(view, appendMethod){
  308. this.ensureEl();
  309. this.close();
  310. this.open(view, appendMethod);
  311. this.currentView = view;
  312. },
  313. ensureEl: function(){
  314. if (!this.$el || this.$el.length == 0){
  315. this.$el = this.getEl(this.el);
  316. }
  317. },
  318. // Override this method to change how the region finds the
  319. // DOM element that it manages. Return a jQuery selector object.
  320. getEl: function(selector){
  321. return $(selector);
  322. },
  323. // Internal method to render and display a view. Not meant
  324. // to be called from any external code.
  325. open: function(view, appendMethod){
  326. var that = this;
  327. appendMethod = appendMethod || "html";
  328. $.when(view.render()).then(function () {
  329. that.$el[appendMethod](view.el);
  330. view.onShow && view.onShow();
  331. view.trigger("show");
  332. that.trigger("view:show", view);
  333. });
  334. },
  335. // Close the current view, if there is one. If there is no
  336. // current view, it does nothing and returns immediately.
  337. close: function(){
  338. var view = this.currentView;
  339. if (!view){ return; }
  340. view.close && view.close();
  341. this.trigger("view:closed", view);
  342. delete this.currentView;
  343. },
  344. // Attach an existing view to the region. This
  345. // will not call `render` or `onShow` for the new view,
  346. // and will not replace the current HTML for the `el`
  347. // of the region.
  348. attachView: function(view){
  349. this.currentView = view;
  350. }
  351. });
  352. // Layout
  353. // ------
  354. // Formerly known as Composite Region.
  355. //
  356. // Used for managing application layouts, nested layouts and
  357. // multiple regions within an application or sub-application.
  358. //
  359. // A specialized view type that renders an area of HTML and then
  360. // attaches `Region` instances to the specified `regions`.
  361. // Used for composite view management and sub-application areas.
  362. Marionette.Layout = Marionette.ItemView.extend({
  363. constructor: function () {
  364. this.vent = new Backbone.Marionette.EventAggregator();
  365. Backbone.Marionette.ItemView.apply(this, arguments);
  366. this.regionManagers = {};
  367. },
  368. render: function () {
  369. this.initializeRegions();
  370. return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
  371. },
  372. close: function () {
  373. this.closeRegions();
  374. Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
  375. },
  376. initializeRegions: function () {
  377. var that = this;
  378. _.each(this.regions, function (selector, name) {
  379. var regionManager = new Backbone.Marionette.Region({
  380. el: selector,
  381. getEl: function(selector){
  382. return that.$(selector);
  383. }
  384. });
  385. that.regionManagers[name] = regionManager;
  386. that[name] = regionManager;
  387. });
  388. },
  389. closeRegions: function () {
  390. var that = this;
  391. _.each(this.regionManagers, function (manager, name) {
  392. manager.close();
  393. delete that[name];
  394. });
  395. this.regionManagers = {};
  396. }
  397. });
  398. // AppRouter
  399. // ---------
  400. // Reduce the boilerplate code of handling route events
  401. // and then calling a single method on another object.
  402. // Have your routers configured to call the method on
  403. // your object, directly.
  404. //
  405. // Configure an AppRouter with `appRoutes`.
  406. //
  407. // App routers can only take one `controller` object.
  408. // It is reocmmended that you divide your controller
  409. // objects in to smaller peices of related functionality
  410. // and have multiple routers / controllers, instead of
  411. // just one giant router and controller.
  412. //
  413. // You can also add standard routes to an AppRouter.
  414. Marionette.AppRouter = Backbone.Router.extend({
  415. constructor: function(options){
  416. Backbone.Router.prototype.constructor.call(this, options);
  417. if (this.appRoutes){
  418. var controller = this.controller;
  419. if (options && options.controller) {
  420. controller = options.controller;
  421. }
  422. this.processAppRoutes(controller, this.appRoutes);
  423. }
  424. },
  425. processAppRoutes: function(controller, appRoutes){
  426. var method, methodName;
  427. var route, routesLength;
  428. var routes = [];
  429. var router = this;
  430. for(route in appRoutes){
  431. routes.unshift([route, appRoutes[route]]);
  432. }
  433. routesLength = routes.length;
  434. for (var i = 0; i < routesLength; i++){
  435. route = routes[i][0];
  436. methodName = routes[i][1];
  437. method = _.bind(controller[methodName], controller);
  438. router.route(route, methodName, method);
  439. }
  440. }
  441. });
  442. // Composite Application
  443. // ---------------------
  444. // Contain and manage the composite application as a whole.
  445. // Stores and starts up `Region` objects, includes an
  446. // event aggregator as `app.vent`
  447. Marionette.Application = function(options){
  448. this.initCallbacks = new Marionette.Callbacks();
  449. this.vent = new Marionette.EventAggregator();
  450. _.extend(this, options);
  451. };
  452. _.extend(Marionette.Application.prototype, Backbone.Events, {
  453. // Add an initializer that is either run at when the `start`
  454. // method is called, or run immediately if added after `start`
  455. // has already been called.
  456. addInitializer: function(initializer){
  457. this.initCallbacks.add(initializer);
  458. },
  459. // kick off all of the application's processes.
  460. // initializes all of the regions that have been added
  461. // to the app, and runs all of the initializer functions
  462. start: function(options){
  463. this.trigger("initialize:before", options);
  464. this.initCallbacks.run(this, options);
  465. this.trigger("initialize:after", options);
  466. this.trigger("start", options);
  467. },
  468. // Add regions to your app.
  469. // Accepts a hash of named strings or Region objects
  470. // addRegions({something: "#someRegion"})
  471. // addRegions{{something: Region.extend({el: "#someRegion"}) });
  472. addRegions: function(regions){
  473. var regionValue, regionObj;
  474. for(var region in regions){
  475. if (regions.hasOwnProperty(region)){
  476. regionValue = regions[region];
  477. if (typeof regionValue === "string"){
  478. regionObj = new Marionette.Region({
  479. el: regionValue
  480. });
  481. } else {
  482. regionObj = new regionValue;
  483. }
  484. this[region] = regionObj;
  485. }
  486. }
  487. }
  488. });
  489. // BindTo: Event Binding
  490. // ---------------------
  491. // BindTo facilitates the binding and unbinding of events
  492. // from objects that extend `Backbone.Events`. It makes
  493. // unbinding events, even with anonymous callback functions,
  494. // easy.
  495. //
  496. // Thanks to Johnny Oshika for this code.
  497. // http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
  498. Marionette.BindTo = {
  499. // Store the event binding in array so it can be unbound
  500. // easily, at a later point in time.
  501. bindTo: function (obj, eventName, callback, context) {
  502. context = context || this;
  503. obj.on(eventName, callback, context);
  504. if (!this.bindings) this.bindings = [];
  505. this.bindings.push({
  506. obj: obj,
  507. eventName: eventName,
  508. callback: callback,
  509. context: context
  510. });
  511. },
  512. // Unbind all of the events that we have stored.
  513. unbindAll: function () {
  514. _.each(this.bindings, function (binding) {
  515. binding.obj.off(binding.eventName, binding.callback);
  516. });
  517. this.bindings = [];
  518. }
  519. };
  520. // Callbacks
  521. // ---------
  522. // A simple way of managing a collection of callbacks
  523. // and executing them at a later point in time, using jQuery's
  524. // `Deferred` object.
  525. Marionette.Callbacks = function(){
  526. this.deferred = $.Deferred();
  527. this.promise = this.deferred.promise();
  528. };
  529. _.extend(Marionette.Callbacks.prototype, {
  530. // Add a callback to be executed. Callbacks added here are
  531. // guaranteed to execute, even if they are added after the
  532. // `run` method is called.
  533. add: function(callback){
  534. this.promise.done(function(context, options){
  535. callback.call(context, options);
  536. });
  537. },
  538. // Run all registered callbacks with the context specified.
  539. // Additional callbacks can be added after this has been run
  540. // and they will still be executed.
  541. run: function(context, options){
  542. this.deferred.resolve(context, options);
  543. }
  544. });
  545. // Event Aggregator
  546. // ----------------
  547. // A pub-sub object that can be used to decouple various parts
  548. // of an application through event-driven architecture.
  549. Marionette.EventAggregator = function(options){
  550. _.extend(this, options);
  551. };
  552. _.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
  553. // Assumes the event aggregator itself is the
  554. // object being bound to.
  555. bindTo: function(eventName, callback, context){
  556. Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
  557. }
  558. });
  559. // Template Cache
  560. // --------------
  561. // Manage templates stored in `<script>` blocks,
  562. // caching them for faster access.
  563. Marionette.TemplateCache = {
  564. templates: {},
  565. loaders: {},
  566. // Get the specified template by id. Either
  567. // retrieves the cached version, or loads it
  568. // from the DOM.
  569. get: function(templateId){
  570. var that = this;
  571. var templateRetrieval = $.Deferred();
  572. var cachedTemplate = this.templates[templateId];
  573. if (cachedTemplate){
  574. templateRetrieval.resolve(cachedTemplate);
  575. } else {
  576. var loader = this.loaders[templateId];
  577. if(loader) {
  578. templateRetrieval = loader;
  579. } else {
  580. this.loaders[templateId] = templateRetrieval;
  581. this.loadTemplate(templateId, function(template){
  582. delete that.loaders[templateId];
  583. that.templates[templateId] = template;
  584. templateRetrieval.resolve(template);
  585. });
  586. }
  587. }
  588. return templateRetrieval.promise();
  589. },
  590. // Load a template from the DOM, by default. Override
  591. // this method to provide your own template retrieval,
  592. // such as asynchronous loading from a server.
  593. loadTemplate: function(templateId, callback){
  594. var template = $(templateId).html();
  595. callback.call(this, template);
  596. },
  597. // Clear templates from the cache. If no arguments
  598. // are specified, clears all templates:
  599. // `clear()`
  600. //
  601. // If arguments are specified, clears each of the
  602. // specified templates from the cache:
  603. // `clear("#t1", "#t2", "...")`
  604. clear: function(){
  605. var length = arguments.length;
  606. if (length > 0){
  607. for(var i=0; i<length; i++){
  608. delete this.templates[arguments[i]];
  609. }
  610. } else {
  611. this.templates = {};
  612. }
  613. }
  614. };
  615. // Renderer
  616. // --------
  617. // Render a template with data by passing in the template
  618. // selector and the data to render.
  619. Marionette.Renderer = {
  620. // Render a template with data. The `template` parameter is
  621. // passed to the `TemplateCache` object to retrieve the
  622. // actual template. Override this method to provide your own
  623. // custom rendering and template handling for all of Marionette.
  624. render: function(template, data){
  625. var that = this;
  626. var asyncRender = $.Deferred();
  627. var templateRetrieval = Marionette.TemplateCache.get(template);
  628. $.when(templateRetrieval).then(function(template){
  629. var html = that.renderTemplate(template, data);
  630. asyncRender.resolve(html);
  631. });
  632. return asyncRender.promise();
  633. },
  634. // Default implementation uses underscore.js templates. Override
  635. // this method to use your own templating engine.
  636. renderTemplate: function(template, data){
  637. if (!template || template.length === 0){
  638. var msg = "A template must be specified";
  639. var err = new Error(msg);
  640. err.name = "NoTemplateError";
  641. throw err;
  642. }
  643. var html = _.template(template, data);
  644. return html;
  645. }
  646. }
  647. // Helpers
  648. // -------
  649. // For slicing `arguments` in functions
  650. var slice = Array.prototype.slice;
  651. // Copy the `extend` function used by Backbone's classes
  652. var extend = Marionette.View.extend;
  653. Marionette.Region.extend = extend;
  654. Marionette.Application.extend = extend;
  655. // Copy the features of `BindTo` on to these objects
  656. _.extend(Marionette.View.prototype, Marionette.BindTo);
  657. _.extend(Marionette.Application.prototype, Marionette.BindTo);
  658. _.extend(Marionette.Region.prototype, Marionette.BindTo);
  659. return Marionette;
  660. })(Backbone, _, window.jQuery || window.Zepto || window.ender);
  661. ;