PageRenderTime 116ms CodeModel.GetById 28ms RepoModel.GetById 2ms app.codeStats 1ms

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

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