PageRenderTime 48ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/ajax/libs/backbone.marionette/0.7.1/amd/backbone.marionette.js

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