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

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

https://gitlab.com/Blueprint-Marketing/cdnjs
JavaScript | 1167 lines | 632 code | 214 blank | 321 comment | 88 complexity | 69f536ac2ba7dc0a0427d619cb31e4e9 MD5 | raw file
  1. // Backbone.Marionette v0.9.3
  2. //
  3. // Copyright (C)2012 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. // BindTo: Event Binding
  21. // ---------------------
  22. // BindTo facilitates the binding and unbinding of events
  23. // from objects that extend `Backbone.Events`. It makes
  24. // unbinding events, even with anonymous callback functions,
  25. // easy.
  26. //
  27. // Thanks to Johnny Oshika for this code.
  28. // http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
  29. Marionette.BindTo = {
  30. // Store the event binding in array so it can be unbound
  31. // easily, at a later point in time.
  32. bindTo: function (obj, eventName, callback, context) {
  33. context = context || this;
  34. obj.on(eventName, callback, context);
  35. if (!this.bindings) { this.bindings = []; }
  36. var binding = {
  37. obj: obj,
  38. eventName: eventName,
  39. callback: callback,
  40. context: context
  41. }
  42. this.bindings.push(binding);
  43. return binding;
  44. },
  45. // Unbind from a single binding object. Binding objects are
  46. // returned from the `bindTo` method call.
  47. unbindFrom: function(binding){
  48. binding.obj.off(binding.eventName, binding.callback, binding.context);
  49. this.bindings = _.reject(this.bindings, function(bind){return bind === binding});
  50. },
  51. // Unbind all of the events that we have stored.
  52. unbindAll: function () {
  53. var that = this;
  54. // The `unbindFrom` call removes elements from the array
  55. // while it is being iterated, so clone it first.
  56. var bindings = _.map(this.bindings, _.identity);
  57. _.each(bindings, function (binding, index) {
  58. that.unbindFrom(binding);
  59. });
  60. }
  61. };
  62. // Marionette.View
  63. // ---------------
  64. // The core view type that other Marionette views extend from.
  65. Marionette.View = Backbone.View.extend({
  66. constructor: function(){
  67. Backbone.View.prototype.constructor.apply(this, arguments);
  68. this.bindTo(this, "show", this.onShowCalled, this);
  69. },
  70. // Get the template for this view
  71. // instance. You can set a `template` attribute in the view
  72. // definition or pass a `template: "whatever"` parameter in
  73. // to the constructor options.
  74. getTemplate: function(){
  75. var template;
  76. // Get the template from `this.options.template` or
  77. // `this.template`. The `options` takes precedence.
  78. if (this.options && this.options.template){
  79. template = this.options.template;
  80. } else {
  81. template = this.template;
  82. }
  83. return template;
  84. },
  85. // Serialize the model or collection for the view. If a model is
  86. // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
  87. // is also called, but is used to populate an `items` array in the
  88. // resulting data. If both are found, defaults to the model.
  89. // You can override the `serializeData` method in your own view
  90. // definition, to provide custom serialization for your view's data.
  91. serializeData: function(){
  92. var data;
  93. if (this.model) {
  94. data = this.model.toJSON();
  95. }
  96. else if (this.collection) {
  97. data = { items: this.collection.toJSON() };
  98. }
  99. data = this.mixinTemplateHelpers(data);
  100. return data;
  101. },
  102. // Mix in template helper methods. Looks for a
  103. // `templateHelpers` attribute, which can either be an
  104. // object literal, or a function that returns an object
  105. // literal. All methods and attributes from this object
  106. // are copies to the object passed in.
  107. mixinTemplateHelpers: function(target){
  108. target = target || {};
  109. var templateHelpers = this.templateHelpers;
  110. if (_.isFunction(templateHelpers)){
  111. templateHelpers = templateHelpers.call(this);
  112. }
  113. return _.extend(target, templateHelpers);
  114. },
  115. // Configure `triggers` to forward DOM events to view
  116. // events. `triggers: {"click .foo": "do:foo"}`
  117. configureTriggers: function(){
  118. if (!this.triggers) { return; }
  119. var triggers = this.triggers;
  120. var that = this;
  121. var triggerEvents = {};
  122. // Allow `triggers` to be configured as a function
  123. if (_.isFunction(triggers)){ triggers = triggers.call(this); }
  124. // Configure the triggers, prevent default
  125. // action and stop propagation of DOM events
  126. _.each(triggers, function(value, key){
  127. triggerEvents[key] = function(e){
  128. if (e && e.preventDefault){ e.preventDefault(); }
  129. if (e && e.stopPropagation){ e.stopPropagation(); }
  130. that.trigger(value);
  131. }
  132. });
  133. return triggerEvents;
  134. },
  135. // Overriding Backbone.View's delegateEvents specifically
  136. // to handle the `triggers` configuration
  137. delegateEvents: function(events){
  138. events = events || this.events;
  139. if (_.isFunction(events)){ events = events.call(this)}
  140. var combinedEvents = {};
  141. var triggers = this.configureTriggers();
  142. _.extend(combinedEvents, events, triggers);
  143. Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
  144. },
  145. // Internal method, handles the `show` event.
  146. onShowCalled: function(){},
  147. // Default `close` implementation, for removing a view from the
  148. // DOM and unbinding it. Regions will call this method
  149. // for you. You can specify an `onClose` method in your view to
  150. // add custom code that is called after the view is closed.
  151. close: function(){
  152. if (this.beforeClose) { this.beforeClose(); }
  153. this.remove();
  154. if (this.onClose) { this.onClose(); }
  155. this.trigger('close');
  156. this.unbindAll();
  157. this.unbind();
  158. }
  159. });
  160. // Copy the features of `BindTo`
  161. _.extend(Marionette.View.prototype, Marionette.BindTo);
  162. // Item View
  163. // ---------
  164. // A single item view implementation that contains code for rendering
  165. // with underscore.js templates, serializing the view's model or collection,
  166. // and calling several methods on extended views, such as `onRender`.
  167. Marionette.ItemView = Marionette.View.extend({
  168. constructor: function(){
  169. Marionette.View.prototype.constructor.apply(this, arguments);
  170. this.initialEvents();
  171. },
  172. // Configured the initial events that the item view
  173. // binds to. Override this method to prevent the initial
  174. // events, or to add your own initial events.
  175. initialEvents: function(){
  176. if (this.collection){
  177. this.bindTo(this.collection, "reset", this.render, this);
  178. }
  179. },
  180. // Render the view, defaulting to underscore.js templates.
  181. // You can override this in your view definition to provide
  182. // a very specific rendering for your view. In general, though,
  183. // you should override the `Marionette.Renderer` object to
  184. // change how Marionette renders views.
  185. render: function(){
  186. if (this.beforeRender){ this.beforeRender(); }
  187. this.trigger("before:render", this);
  188. this.trigger("item:before:render", this);
  189. var data = this.serializeData();
  190. var template = this.getTemplate();
  191. var html = Marionette.Renderer.render(template, data);
  192. this.$el.html(html);
  193. if (this.onRender){ this.onRender(); }
  194. this.trigger("render", this);
  195. this.trigger("item:rendered", this);
  196. return this;
  197. },
  198. // Override the default close event to add a few
  199. // more events that are triggered.
  200. close: function(){
  201. this.trigger('item:before:close');
  202. Marionette.View.prototype.close.apply(this, arguments);
  203. this.trigger('item:closed');
  204. }
  205. });
  206. // Collection View
  207. // ---------------
  208. // A view that iterates over a Backbone.Collection
  209. // and renders an individual ItemView for each model.
  210. Marionette.CollectionView = Marionette.View.extend({
  211. constructor: function(){
  212. Marionette.View.prototype.constructor.apply(this, arguments);
  213. this.initChildViewStorage();
  214. this.initialEvents();
  215. this.onShowCallbacks = new Marionette.Callbacks();
  216. },
  217. // Configured the initial events that the collection view
  218. // binds to. Override this method to prevent the initial
  219. // events, or to add your own initial events.
  220. initialEvents: function(){
  221. if (this.collection){
  222. this.bindTo(this.collection, "add", this.addChildView, this);
  223. this.bindTo(this.collection, "remove", this.removeItemView, this);
  224. this.bindTo(this.collection, "reset", this.render, this);
  225. }
  226. },
  227. // Handle a child item added to the collection
  228. addChildView: function(item, collection, options){
  229. this.closeEmptyView();
  230. var ItemView = this.getItemView();
  231. return this.addItemView(item, ItemView, options.index);
  232. },
  233. // Override from `Marionette.View` to guarantee the `onShow` method
  234. // of child views is called.
  235. onShowCalled: function(){
  236. this.onShowCallbacks.run();
  237. },
  238. // Internal method to trigger the before render callbacks
  239. // and events
  240. triggerBeforeRender: function(){
  241. if (this.beforeRender) { this.beforeRender(); }
  242. this.trigger("before:render", this);
  243. this.trigger("collection:before:render", this);
  244. },
  245. // Internal method to trigger the rendered callbacks and
  246. // events
  247. triggerRendered: function(){
  248. if (this.onRender) { this.onRender(); }
  249. this.trigger("render", this);
  250. this.trigger("collection:rendered", this);
  251. },
  252. // Render the collection of items. Override this method to
  253. // provide your own implementation of a render function for
  254. // the collection view.
  255. render: function(){
  256. this.triggerBeforeRender();
  257. this.closeChildren();
  258. if (this.collection && this.collection.length > 0) {
  259. this.showCollection();
  260. } else {
  261. this.showEmptyView();
  262. }
  263. this.triggerRendered();
  264. return this;
  265. },
  266. // Internal method to loop through each item in the
  267. // collection view and show it
  268. showCollection: function(){
  269. var that = this;
  270. var ItemView = this.getItemView();
  271. this.collection.each(function(item, index){
  272. that.addItemView(item, ItemView, index);
  273. });
  274. },
  275. // Internal method to show an empty view in place of
  276. // a collection of item views, when the collection is
  277. // empty
  278. showEmptyView: function(){
  279. var EmptyView = this.options.emptyView || this.emptyView;
  280. if (EmptyView){
  281. this.showingEmptyView = true;
  282. var model = new Backbone.Model();
  283. this.addItemView(model, EmptyView, 0);
  284. }
  285. },
  286. // Internal method to close an existing emptyView instance
  287. // if one exists. Called when a collection view has been
  288. // rendered empty, and then an item is added to the collection.
  289. closeEmptyView: function(){
  290. if (this.showingEmptyView){
  291. this.closeChildren();
  292. delete this.showingEmptyView;
  293. }
  294. },
  295. // Retrieve the itemView type, either from `this.options.itemView`
  296. // or from the `itemView` in the object definition. The "options"
  297. // takes precedence.
  298. getItemView: function(){
  299. var itemView = this.options.itemView || this.itemView;
  300. if (!itemView){
  301. var err = new Error("An `itemView` must be specified");
  302. err.name = "NoItemViewError";
  303. throw err;
  304. }
  305. return itemView;
  306. },
  307. // Render the child item's view and add it to the
  308. // HTML for the collection view.
  309. addItemView: function(item, ItemView, index){
  310. var that = this;
  311. var view = this.buildItemView(item, ItemView);
  312. // Store the child view itself so we can properly
  313. // remove and/or close it later
  314. this.storeChild(view);
  315. if (this.onItemAdded){ this.onItemAdded(view); }
  316. this.trigger("item:added", view);
  317. // Render it and show it
  318. var renderResult = this.renderItemView(view, index);
  319. // call onShow for child item views
  320. if (view.onShow){
  321. this.onShowCallbacks.add(view.onShow, view);
  322. }
  323. // Forward all child item view events through the parent,
  324. // prepending "itemview:" to the event name
  325. var childBinding = this.bindTo(view, "all", function(){
  326. var args = slice.call(arguments);
  327. args[0] = "itemview:" + args[0];
  328. args.splice(1, 0, view);
  329. that.trigger.apply(that, args);
  330. });
  331. // Store all child event bindings so we can unbind
  332. // them when removing / closing the child view
  333. this.childBindings = this.childBindings || {};
  334. this.childBindings[view.cid] = childBinding;
  335. return renderResult;
  336. },
  337. // render the item view
  338. renderItemView: function(view, index) {
  339. view.render();
  340. this.appendHtml(this, view, index);
  341. },
  342. // Build an `itemView` for every model in the collection.
  343. buildItemView: function(item, ItemView){
  344. var itemViewOptions = _.result(this, "itemViewOptions");
  345. var options = _.extend({model: item}, itemViewOptions);
  346. var view = new ItemView(options);
  347. return view;
  348. },
  349. // Remove the child view and close it
  350. removeItemView: function(item){
  351. var view = this.children[item.cid];
  352. if (view){
  353. var childBinding = this.childBindings[view.cid];
  354. if (childBinding) {
  355. this.unbindFrom(childBinding);
  356. delete this.childBindings[view.cid];
  357. }
  358. view.close();
  359. delete this.children[item.cid];
  360. }
  361. if (this.collection.length === 0){
  362. this.showEmptyView();
  363. }
  364. this.trigger("item:removed", view);
  365. },
  366. // Append the HTML to the collection's `el`.
  367. // Override this method to do something other
  368. // then `.append`.
  369. appendHtml: function(collectionView, itemView, index){
  370. collectionView.$el.append(itemView.el);
  371. },
  372. // Store references to all of the child `itemView`
  373. // instances so they can be managed and cleaned up, later.
  374. storeChild: function(view){
  375. this.children[view.model.cid] = view;
  376. },
  377. // Internal method to set up the `children` object for
  378. // storing all of the child views
  379. initChildViewStorage: function(){
  380. this.children = {};
  381. },
  382. // Handle cleanup and other closing needs for
  383. // the collection of views.
  384. close: function(){
  385. this.trigger("collection:before:close");
  386. this.closeChildren();
  387. Marionette.View.prototype.close.apply(this, arguments);
  388. this.trigger("collection:closed");
  389. },
  390. // Close the child views that this collection view
  391. // is holding on to, if any
  392. closeChildren: function(){
  393. var that = this;
  394. if (this.children){
  395. _.each(_.clone(this.children), function(childView){
  396. that.removeItemView(childView.model);
  397. });
  398. }
  399. }
  400. });
  401. // Composite View
  402. // --------------
  403. // Used for rendering a branch-leaf, hierarchical structure.
  404. // Extends directly from CollectionView and also renders an
  405. // an item view as `modelView`, for the top leaf
  406. Marionette.CompositeView = Marionette.CollectionView.extend({
  407. constructor: function(options){
  408. Marionette.CollectionView.apply(this, arguments);
  409. this.itemView = this.getItemView();
  410. },
  411. // Configured the initial events that the composite view
  412. // binds to. Override this method to prevent the initial
  413. // events, or to add your own initial events.
  414. initialEvents: function(){
  415. if (this.collection){
  416. this.bindTo(this.collection, "add", this.addChildView, this);
  417. this.bindTo(this.collection, "remove", this.removeItemView, this);
  418. this.bindTo(this.collection, "reset", this.renderCollection, this);
  419. }
  420. },
  421. // Retrieve the `itemView` to be used when rendering each of
  422. // the items in the collection. The default is to return
  423. // `this.itemView` or Marionette.CompositeView if no `itemView`
  424. // has been defined
  425. getItemView: function(){
  426. return this.itemView || this.constructor;
  427. },
  428. // Renders the model once, and the collection once. Calling
  429. // this again will tell the model's view to re-render itself
  430. // but the collection will not re-render.
  431. render: function(){
  432. var that = this;
  433. this.resetItemViewContainer();
  434. var html = this.renderModel();
  435. this.$el.html(html);
  436. this.trigger("composite:model:rendered");
  437. this.trigger("render");
  438. this.renderCollection();
  439. this.trigger("composite:rendered");
  440. return this;
  441. },
  442. // Render the collection for the composite view
  443. renderCollection: function(){
  444. Marionette.CollectionView.prototype.render.apply(this, arguments);
  445. this.trigger("composite:collection:rendered");
  446. },
  447. // Render an individual model, if we have one, as
  448. // part of a composite view (branch / leaf). For example:
  449. // a treeview.
  450. renderModel: function(){
  451. var data = {};
  452. data = this.serializeData();
  453. var template = this.getTemplate();
  454. return Marionette.Renderer.render(template, data);
  455. },
  456. // Appends the `el` of itemView instances to the specified
  457. // `itemViewContainer` (a jQuery selector). Override this method to
  458. // provide custom logic of how the child item view instances have their
  459. // HTML appended to the composite view instance.
  460. appendHtml: function(cv, iv){
  461. var $container = this.getItemViewContainer(cv);
  462. $container.append(iv.el);
  463. },
  464. // Internal method to ensure an `$itemViewContainer` exists, for the
  465. // `appendHtml` method to use.
  466. getItemViewContainer: function(containerView){
  467. var container;
  468. if ("$itemViewContainer" in containerView){
  469. container = containerView.$itemViewContainer;
  470. } else {
  471. if (containerView.itemViewContainer){
  472. container = containerView.$(_.result(containerView, "itemViewContainer"));
  473. } else {
  474. container = containerView.$el;
  475. }
  476. containerView.$itemViewContainer = container;
  477. }
  478. return container;
  479. },
  480. // Internal method to reset the `$itemViewContainer` on render
  481. resetItemViewContainer: function(){
  482. if (this.$itemViewContainer){
  483. delete this.$itemViewContainer;
  484. }
  485. }
  486. });
  487. // Region
  488. // ------
  489. // Manage the visual regions of your composite application. See
  490. // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
  491. Marionette.Region = function(options){
  492. this.options = options || {};
  493. _.extend(this, options);
  494. if (!this.el){
  495. var err = new Error("An 'el' must be specified");
  496. err.name = "NoElError";
  497. throw err;
  498. }
  499. if (this.initialize){
  500. this.initialize.apply(this, arguments);
  501. }
  502. };
  503. _.extend(Marionette.Region.prototype, Backbone.Events, {
  504. // Displays a backbone view instance inside of the region.
  505. // Handles calling the `render` method for you. Reads content
  506. // directly from the `el` attribute. Also calls an optional
  507. // `onShow` and `close` method on your view, just after showing
  508. // or just before closing the view, respectively.
  509. show: function(view){
  510. var that = this;
  511. this.ensureEl();
  512. this.close();
  513. view.render();
  514. this.open(view);
  515. if (view.onShow) { view.onShow(); }
  516. view.trigger("show");
  517. if (this.onShow) { this.onShow(view); }
  518. this.trigger("view:show", view);
  519. this.currentView = view;
  520. },
  521. ensureEl: function(){
  522. if (!this.$el || this.$el.length === 0){
  523. this.$el = this.getEl(this.el);
  524. }
  525. },
  526. // Override this method to change how the region finds the
  527. // DOM element that it manages. Return a jQuery selector object.
  528. getEl: function(selector){
  529. return $(selector);
  530. },
  531. // Override this method to change how the new view is
  532. // appended to the `$el` that the region is managing
  533. open: function(view){
  534. this.$el.html(view.el);
  535. },
  536. // Close the current view, if there is one. If there is no
  537. // current view, it does nothing and returns immediately.
  538. close: function(){
  539. var view = this.currentView;
  540. if (!view){ return; }
  541. if (view.close) { view.close(); }
  542. this.trigger("view:closed", view);
  543. delete this.currentView;
  544. },
  545. // Attach an existing view to the region. This
  546. // will not call `render` or `onShow` for the new view,
  547. // and will not replace the current HTML for the `el`
  548. // of the region.
  549. attachView: function(view){
  550. this.currentView = view;
  551. }
  552. });
  553. // Copy the `extend` function used by Backbone's classes
  554. Marionette.Region.extend = Backbone.View.extend;
  555. // Copy the features of `BindTo`
  556. _.extend(Marionette.Region.prototype, Marionette.BindTo);
  557. // Layout
  558. // ------
  559. // Used for managing application layouts, nested layouts and
  560. // multiple regions within an application or sub-application.
  561. //
  562. // A specialized view type that renders an area of HTML and then
  563. // attaches `Region` instances to the specified `regions`.
  564. // Used for composite view management and sub-application areas.
  565. Marionette.Layout = Marionette.ItemView.extend({
  566. constructor: function () {
  567. Backbone.Marionette.ItemView.apply(this, arguments);
  568. this.initializeRegions();
  569. },
  570. // Layout's render will use the existing region objects the
  571. // first time it is called. Subsequent calls will close the
  572. // views that the regions are showing and then reset the `el`
  573. // for the regions to the newly rendered DOM elements.
  574. render: function(){
  575. var result = Marionette.ItemView.prototype.render.apply(this, arguments);
  576. // Rewrite this function to handle re-rendering and
  577. // re-initializing the `el` for each region
  578. this.render = function(){
  579. this.closeRegions();
  580. this.reInitializeRegions();
  581. var result = Marionette.ItemView.prototype.render.apply(this, arguments);
  582. return result;
  583. }
  584. return result;
  585. },
  586. // Handle closing regions, and then close the view itself.
  587. close: function () {
  588. this.closeRegions();
  589. this.destroyRegions();
  590. Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
  591. },
  592. // Initialize the regions that have been defined in a
  593. // `regions` attribute on this layout. The key of the
  594. // hash becomes an attribute on the layout object directly.
  595. // For example: `regions: { menu: ".menu-container" }`
  596. // will product a `layout.menu` object which is a region
  597. // that controls the `.menu-container` DOM element.
  598. initializeRegions: function () {
  599. if (!this.regionManagers){
  600. this.regionManagers = {};
  601. }
  602. var that = this;
  603. _.each(this.regions, function (selector, name) {
  604. var regionManager = new Backbone.Marionette.Region({
  605. el: selector,
  606. getEl: function(selector){
  607. return that.$(selector);
  608. }
  609. });
  610. that.regionManagers[name] = regionManager;
  611. that[name] = regionManager;
  612. });
  613. },
  614. // Re-initialize all of the regions by updating the `el` that
  615. // they point to
  616. reInitializeRegions: function(){
  617. _.each(this.regionManagers, function(region){
  618. delete region.$el;
  619. });
  620. },
  621. // Close all of the regions that have been opened by
  622. // this layout. This method is called when the layout
  623. // itself is closed.
  624. closeRegions: function () {
  625. var that = this;
  626. _.each(this.regionManagers, function (manager, name) {
  627. manager.close();
  628. });
  629. },
  630. // Destroys all of the regions by removing references
  631. // from the Layout
  632. destroyRegions: function(){
  633. var that = this;
  634. _.each(this.regionManagers, function (manager, name) {
  635. delete that[name];
  636. });
  637. this.regionManagers = {};
  638. }
  639. });
  640. // Application
  641. // -----------
  642. // Contain and manage the composite application as a whole.
  643. // Stores and starts up `Region` objects, includes an
  644. // event aggregator as `app.vent`
  645. Marionette.Application = function(options){
  646. this.initCallbacks = new Marionette.Callbacks();
  647. this.vent = new Marionette.EventAggregator();
  648. _.extend(this, options);
  649. };
  650. _.extend(Marionette.Application.prototype, Backbone.Events, {
  651. // Add an initializer that is either run at when the `start`
  652. // method is called, or run immediately if added after `start`
  653. // has already been called.
  654. addInitializer: function(initializer){
  655. this.initCallbacks.add(initializer);
  656. },
  657. // kick off all of the application's processes.
  658. // initializes all of the regions that have been added
  659. // to the app, and runs all of the initializer functions
  660. start: function(options){
  661. this.trigger("initialize:before", options);
  662. this.initCallbacks.run(options, this);
  663. this.trigger("initialize:after", options);
  664. this.trigger("start", options);
  665. },
  666. // Add regions to your app.
  667. // Accepts a hash of named strings or Region objects
  668. // addRegions({something: "#someRegion"})
  669. // addRegions{{something: Region.extend({el: "#someRegion"}) });
  670. addRegions: function(regions){
  671. var regionValue, regionObj, region;
  672. for(region in regions){
  673. if (regions.hasOwnProperty(region)){
  674. regionValue = regions[region];
  675. if (typeof regionValue === "string"){
  676. regionObj = new Marionette.Region({
  677. el: regionValue
  678. });
  679. } else {
  680. regionObj = new regionValue();
  681. }
  682. this[region] = regionObj;
  683. }
  684. }
  685. },
  686. // Create a module, attached to the application
  687. module: function(){
  688. // see the Marionette.Module object for more information
  689. return Marionette.Module.create.apply(this, arguments);
  690. }
  691. });
  692. // Copy the `extend` function used by Backbone's classes
  693. Marionette.Application.extend = Backbone.View.extend;
  694. // Copy the features of `BindTo`
  695. _.extend(Marionette.Application.prototype, Marionette.BindTo);
  696. // AppRouter
  697. // ---------
  698. // Reduce the boilerplate code of handling route events
  699. // and then calling a single method on another object.
  700. // Have your routers configured to call the method on
  701. // your object, directly.
  702. //
  703. // Configure an AppRouter with `appRoutes`.
  704. //
  705. // App routers can only take one `controller` object.
  706. // It is recommended that you divide your controller
  707. // objects in to smaller peices of related functionality
  708. // and have multiple routers / controllers, instead of
  709. // just one giant router and controller.
  710. //
  711. // You can also add standard routes to an AppRouter.
  712. Marionette.AppRouter = Backbone.Router.extend({
  713. constructor: function(options){
  714. Backbone.Router.prototype.constructor.call(this, options);
  715. if (this.appRoutes){
  716. var controller = this.controller;
  717. if (options && options.controller) {
  718. controller = options.controller;
  719. }
  720. this.processAppRoutes(controller, this.appRoutes);
  721. }
  722. },
  723. // Internal method to process the `appRoutes` for the
  724. // router, and turn them in to routes that trigger the
  725. // specified method on the specified `controller`.
  726. processAppRoutes: function(controller, appRoutes){
  727. var method, methodName;
  728. var route, routesLength, i;
  729. var routes = [];
  730. var router = this;
  731. for(route in appRoutes){
  732. if (appRoutes.hasOwnProperty(route)){
  733. routes.unshift([route, appRoutes[route]]);
  734. }
  735. }
  736. routesLength = routes.length;
  737. for (i = 0; i < routesLength; i++){
  738. route = routes[i][0];
  739. methodName = routes[i][1];
  740. method = controller[methodName];
  741. if (!method){
  742. var msg = "Method '" + methodName + "' was not found on the controller";
  743. var err = new Error(msg);
  744. err.name = "NoMethodError";
  745. throw err;
  746. }
  747. method = _.bind(method, controller);
  748. router.route(route, methodName, method);
  749. }
  750. }
  751. });
  752. // Module
  753. // ------
  754. // A simple module system, used to create privacy and encapsulation in
  755. // Marionette applications
  756. Marionette.Module = function(){};
  757. // Extend the Module prototype with events / bindTo, so that the module
  758. // can be used as an event aggregator or pub/sub.
  759. _.extend(Marionette.Module.prototype, Backbone.Events, Marionette.BindTo);
  760. // Function level methods to create modules
  761. _.extend(Marionette.Module, {
  762. // Create a module, hanging off 'this' as the parent object. This
  763. // method must be called with .apply or .create
  764. create: function(moduleNames, moduleDefinition){
  765. var moduleName, module, moduleOverride;
  766. var parentObject = this;
  767. var parentModule = this;
  768. var moduleNames = moduleNames.split(".");
  769. // Loop through all the parts of the module definition
  770. var length = moduleNames.length;
  771. for(var i = 0; i < length; i++){
  772. var isLastModuleInChain = (i === length-1);
  773. // Get the module name, and check if it exists on
  774. // the current parent already
  775. moduleName = moduleNames[i];
  776. module = parentModule[moduleName];
  777. // Create a new module if we don't have one already
  778. if (!module){
  779. module = new Marionette.Module();
  780. }
  781. // Check to see if we need to run the definition
  782. // for the module. Only run the definition if one
  783. // is supplied, and if we're at the last segment
  784. // of the "Module.Name" chain.
  785. if (isLastModuleInChain && moduleDefinition){
  786. // get the custom args passed in after the module definition and
  787. // get rid of the module name and definition function
  788. var customArgs = slice.apply(arguments);
  789. customArgs.shift();
  790. customArgs.shift();
  791. // final arguments list for the module definition
  792. var argsArray = [module, parentObject, Backbone, Marionette, jQuery, _, customArgs];
  793. // flatten the nested array
  794. var args = _.flatten(argsArray);
  795. // ensure the module definition's `this` is the module itself
  796. moduleDefinition.apply(module, args);
  797. }
  798. // If the defined module is not what we are
  799. // currently storing as the module, replace it
  800. if (parentModule[moduleName] !== module){
  801. parentModule[moduleName] = module;
  802. }
  803. // Reset the parent module so that the next child
  804. // in the list will be added to the correct parent
  805. parentModule = module;
  806. }
  807. // Return the last module in the definition chain
  808. return module;
  809. }
  810. });
  811. // Template Cache
  812. // --------------
  813. // Manage templates stored in `<script>` blocks,
  814. // caching them for faster access.
  815. Marionette.TemplateCache = function(templateId){
  816. this.templateId = templateId;
  817. };
  818. // TemplateCache object-level methods. Manage the template
  819. // caches from these method calls instead of creating
  820. // your own TemplateCache instances
  821. _.extend(Marionette.TemplateCache, {
  822. templateCaches: {},
  823. // Get the specified template by id. Either
  824. // retrieves the cached version, or loads it
  825. // from the DOM.
  826. get: function(templateId){
  827. var that = this;
  828. var cachedTemplate = this.templateCaches[templateId];
  829. if (!cachedTemplate){
  830. cachedTemplate = new Marionette.TemplateCache(templateId);
  831. this.templateCaches[templateId] = cachedTemplate;
  832. }
  833. return cachedTemplate.load();
  834. },
  835. // Clear templates from the cache. If no arguments
  836. // are specified, clears all templates:
  837. // `clear()`
  838. //
  839. // If arguments are specified, clears each of the
  840. // specified templates from the cache:
  841. // `clear("#t1", "#t2", "...")`
  842. clear: function(){
  843. var i;
  844. var length = arguments.length;
  845. if (length > 0){
  846. for(i=0; i<length; i++){
  847. delete this.templateCaches[arguments[i]];
  848. }
  849. } else {
  850. this.templateCaches = {};
  851. }
  852. }
  853. });
  854. // TemplateCache instance methods, allowing each
  855. // template cache object to manage it's own state
  856. // and know whether or not it has been loaded
  857. _.extend(Marionette.TemplateCache.prototype, {
  858. // Internal method to load the template asynchronously.
  859. load: function(){
  860. var that = this;
  861. // Guard clause to prevent loading this template more than once
  862. if (this.compiledTemplate){
  863. return this.compiledTemplate;
  864. }
  865. // Load the template and compile it
  866. var template = this.loadTemplate(this.templateId);
  867. this.compiledTemplate = this.compileTemplate(template);
  868. return this.compiledTemplate;
  869. },
  870. // Load a template from the DOM, by default. Override
  871. // this method to provide your own template retrieval,
  872. // such as asynchronous loading from a server.
  873. loadTemplate: function(templateId){
  874. var template = $(templateId).html();
  875. if (!template || template.length === 0){
  876. var msg = "Could not find template: '" + templateId + "'";
  877. var err = new Error(msg);
  878. err.name = "NoTemplateError";
  879. throw err;
  880. }
  881. return template;
  882. },
  883. // Pre-compile the template before caching it. Override
  884. // this method if you do not need to pre-compile a template
  885. // (JST / RequireJS for example) or if you want to change
  886. // the template engine used (Handebars, etc).
  887. compileTemplate: function(rawTemplate){
  888. return _.template(rawTemplate);
  889. }
  890. });
  891. // Renderer
  892. // --------
  893. // Render a template with data by passing in the template
  894. // selector and the data to render.
  895. Marionette.Renderer = {
  896. // Render a template with data. The `template` parameter is
  897. // passed to the `TemplateCache` object to retrieve the
  898. // template function. Override this method to provide your own
  899. // custom rendering and template handling for all of Marionette.
  900. render: function(template, data){
  901. var templateFunc = Marionette.TemplateCache.get(template);
  902. var html = templateFunc(data);
  903. return html;
  904. }
  905. };
  906. // Callbacks
  907. // ---------
  908. // A simple way of managing a collection of callbacks
  909. // and executing them at a later point in time, using jQuery's
  910. // `Deferred` object.
  911. Marionette.Callbacks = function(){
  912. this.deferred = $.Deferred();
  913. this.promise = this.deferred.promise();
  914. };
  915. _.extend(Marionette.Callbacks.prototype, {
  916. // Add a callback to be executed. Callbacks added here are
  917. // guaranteed to execute, even if they are added after the
  918. // `run` method is called.
  919. add: function(callback, contextOverride){
  920. this.promise.done(function(context, options){
  921. if (contextOverride){ context = contextOverride; }
  922. callback.call(context, options);
  923. });
  924. },
  925. // Run all registered callbacks with the context specified.
  926. // Additional callbacks can be added after this has been run
  927. // and they will still be executed.
  928. run: function(options, context){
  929. this.deferred.resolve(context, options);
  930. }
  931. });
  932. // Event Aggregator
  933. // ----------------
  934. // A pub-sub object that can be used to decouple various parts
  935. // of an application through event-driven architecture.
  936. Marionette.EventAggregator = function(options){
  937. _.extend(this, options);
  938. };
  939. _.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
  940. // Assumes the event aggregator itself is the
  941. // object being bound to.
  942. bindTo: function(eventName, callback, context){
  943. return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
  944. }
  945. });
  946. // Helpers
  947. // -------
  948. // For slicing `arguments` in functions
  949. var slice = Array.prototype.slice;
  950. return Marionette;
  951. })(Backbone, _, window.jQuery || window.Zepto || window.ender);
  952. return Backbone.Marionette;
  953. }));