PageRenderTime 60ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/ajax/libs/backbone.marionette/1.0.0-rc2/backbone.marionette.js

https://bitbucket.org/kolbyjAFK/cdnjs
JavaScript | 1764 lines | 919 code | 354 blank | 491 comment | 127 complexity | 67eac14b4fe6292c4dcaba8cf4a80171 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception

Large files files are truncated, but you can click here to view the full file

  1. // Backbone.Marionette, v1.0.0-rc2
  2. // Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
  3. // Distributed under MIT license
  4. // http://github.com/marionettejs/backbone.marionette
  5. Backbone.Marionette = Marionette = (function(Backbone, _, $){
  6. var Marionette = {};
  7. // Helpers
  8. // -------
  9. // For slicing `arguments` in functions
  10. var slice = Array.prototype.slice;
  11. // Marionette.extend
  12. // -----------------
  13. // Borrow the Backbone `extend` method so we can use it as needed
  14. Marionette.extend = Backbone.Model.extend;
  15. // Marionette.getOption
  16. // --------------------
  17. // Retrieve an object, function or other value from a target
  18. // object or it's `options`, with `options` taking precedence.
  19. Marionette.getOption = function(target, optionName){
  20. if (!target || !optionName){ return; }
  21. var value;
  22. if (target.options && target.options[optionName]){
  23. value = target.options[optionName];
  24. } else {
  25. value = target[optionName];
  26. }
  27. return value;
  28. };
  29. // Mairionette.createObject
  30. // ------------------------
  31. // A wrapper / shim for `Object.create`. Uses native `Object.create`
  32. // if available, otherwise shims it in place for Marionette to use.
  33. Marionette.createObject = (function(){
  34. var createObject;
  35. // Define this once, and just replace the .prototype on it as needed,
  36. // to improve performance in older / less optimized JS engines
  37. function F() {}
  38. // Check for existing native / shimmed Object.create
  39. if (typeof Object.create === "function"){
  40. // found native/shim, so use it
  41. createObject = Object.create;
  42. } else {
  43. // An implementation of the Boodman/Crockford delegation
  44. // w/ Cornford optimization, as suggested by @unscriptable
  45. // https://gist.github.com/3959151
  46. // native/shim not found, so shim it ourself
  47. createObject = function (o) {
  48. // set the prototype of the function
  49. // so we will get `o` as the prototype
  50. // of the new object instance
  51. F.prototype = o;
  52. // create a new object that inherits from
  53. // the `o` parameter
  54. var child = new F();
  55. // clean up just in case o is really large
  56. F.prototype = null;
  57. // send it back
  58. return child;
  59. };
  60. }
  61. return createObject;
  62. })();
  63. // Trigger an event and a corresponding method name. Examples:
  64. //
  65. // `this.triggerMethod("foo")` will trigger the "foo" event and
  66. // call the "onFoo" method.
  67. //
  68. // `this.triggerMethod("foo:bar") will trigger the "foo:bar" event and
  69. // call the "onFooBar" method.
  70. Marionette.triggerMethod = function(){
  71. var args = Array.prototype.slice.apply(arguments);
  72. var eventName = args[0];
  73. var segments = eventName.split(":");
  74. var segment, capLetter, methodName = "on";
  75. for (var i = 0; i < segments.length; i++){
  76. segment = segments[i];
  77. capLetter = segment.charAt(0).toUpperCase();
  78. methodName += capLetter + segment.slice(1);
  79. }
  80. this.trigger.apply(this, args);
  81. if (_.isFunction(this[methodName])){
  82. args.shift();
  83. return this[methodName].apply(this, args);
  84. }
  85. };
  86. // DOMRefresh
  87. // ----------
  88. //
  89. // Monitor a view's state, and after it has been rendered and shown
  90. // in the DOM, trigger a "dom:refresh" event every time it is
  91. // re-rendered.
  92. Marionette.MonitorDOMRefresh = (function(){
  93. // track when the view has been rendered
  94. function handleShow(view){
  95. view._isShown = true;
  96. triggerDOMRefresh(view);
  97. }
  98. // track when the view has been shown in the DOM,
  99. // using a Marionette.Region (or by other means of triggering "show")
  100. function handleRender(view){
  101. view._isRendered = true;
  102. triggerDOMRefresh(view);
  103. }
  104. // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
  105. function triggerDOMRefresh(view){
  106. if (view._isShown && view._isRendered){
  107. if (_.isFunction(view.triggerMethod)){
  108. view.triggerMethod("dom:refresh");
  109. }
  110. }
  111. }
  112. // Export public API
  113. return function(view){
  114. view.bindTo(view, "show", function(){
  115. handleShow(view);
  116. });
  117. view.bindTo(view, "render", function(){
  118. handleRender(view);
  119. });
  120. };
  121. })();
  122. // EventBinder
  123. // -----------
  124. // Import the event binder from it's new home
  125. // https://github.com/marionettejs/backbone.eventbinder
  126. Marionette.EventBinder = Backbone.EventBinder.extend();
  127. // Add the EventBinder methods to the view directly,
  128. // but keep them bound to the EventBinder instance so they work properly.
  129. // This allows the event binder's implementation to vary independently
  130. // of it being attached to the view... for example the internal structure
  131. // used to store the events can change without worry about it interfering
  132. // with Marionette's views.
  133. Marionette.addEventBinder = function(target){
  134. var eventBinder = new Marionette.EventBinder();
  135. target.eventBinder = eventBinder;
  136. target.bindTo = function(source, event, callback, context){
  137. // check the context of the bindTo and set it to the object
  138. // that is having the eventBinder attached to it, if no context
  139. // has been specified in the .bindTo call
  140. context = context || target;
  141. eventBinder.bindTo(source, event, callback, context);
  142. };
  143. target.unbindFrom = _.bind(eventBinder.unbindFrom, eventBinder);
  144. target.unbindAll = _.bind(eventBinder.unbindAll, eventBinder);
  145. };
  146. // Event Aggregator
  147. // ----------------
  148. // A pub-sub object that can be used to decouple various parts
  149. // of an application through event-driven architecture.
  150. //
  151. // Extends [Backbone.Wreqr.EventAggregator](https://github.com/marionettejs/backbone.wreqr)
  152. // and mixes in an EventBinder from [Backbone.EventBinder](https://github.com/marionettejs/backbone.eventbinder).
  153. Marionette.EventAggregator = Backbone.Wreqr.EventAggregator.extend({
  154. constructor: function(){
  155. Marionette.addEventBinder(this);
  156. var args = Array.prototype.slice.apply(arguments);
  157. Backbone.Wreqr.EventAggregator.prototype.constructor.apply(this, args);
  158. }
  159. });
  160. // Marionette.bindEntityEvents
  161. // ---------------------------
  162. //
  163. // This method is used to bind a backbone "entity" (collection/model)
  164. // to methods on a target object.
  165. //
  166. // The first paremter, `target`, must have a `bindTo` method from the
  167. // EventBinder object.
  168. //
  169. // The second parameter is the entity (Backbone.Model or Backbone.Collection)
  170. // to bind the events from.
  171. //
  172. // The third parameter is a hash of { "event:name": "eventHandler" }
  173. // configuration. Multiple handlers can be separated by a space. A
  174. // function can be supplied instead of a string handler name.
  175. Marionette.bindEntityEvents = (function(){
  176. // Bind the event to handlers specified as a string of
  177. // handler names on the target object
  178. function bindFromStrings(target, entity, evt, methods){
  179. var methodNames = methods.split(/\s+/);
  180. _.each(methodNames,function(methodName) {
  181. var method = target[methodName];
  182. if(!method) {
  183. throw new Error("Method '"+ methodName +"' was configured as an event handler, but does not exist.");
  184. }
  185. target.bindTo(entity, evt, method, target);
  186. });
  187. }
  188. // Bind the event to a supplied callback function
  189. function bindToFunction(target, entity, evt, method){
  190. target.bindTo(entity, evt, method, target);
  191. }
  192. // Export the bindEntityEvents method
  193. return function(target, entity, bindings){
  194. if (!entity || !bindings) { return; }
  195. _.each(bindings, function(methods, evt){
  196. // allow for a function as the handler,
  197. // or a list of event names as a string
  198. if (_.isFunction(methods)){
  199. bindToFunction(target, entity, evt, methods);
  200. } else {
  201. bindFromStrings(target, entity, evt, methods);
  202. }
  203. });
  204. };
  205. })();
  206. // Callbacks
  207. // ---------
  208. // A simple way of managing a collection of callbacks
  209. // and executing them at a later point in time, using jQuery's
  210. // `Deferred` object.
  211. Marionette.Callbacks = function(){
  212. this._deferred = $.Deferred();
  213. this._callbacks = [];
  214. };
  215. _.extend(Marionette.Callbacks.prototype, {
  216. // Add a callback to be executed. Callbacks added here are
  217. // guaranteed to execute, even if they are added after the
  218. // `run` method is called.
  219. add: function(callback, contextOverride){
  220. this._callbacks.push({cb: callback, ctx: contextOverride});
  221. this._deferred.done(function(context, options){
  222. if (contextOverride){ context = contextOverride; }
  223. callback.call(context, options);
  224. });
  225. },
  226. // Run all registered callbacks with the context specified.
  227. // Additional callbacks can be added after this has been run
  228. // and they will still be executed.
  229. run: function(options, context){
  230. this._deferred.resolve(context, options);
  231. },
  232. // Resets the list of callbacks to be run, allowing the same list
  233. // to be run multiple times - whenever the `run` method is called.
  234. reset: function(){
  235. var that = this;
  236. var callbacks = this._callbacks;
  237. this._deferred = $.Deferred();
  238. this._callbacks = [];
  239. _.each(callbacks, function(cb){
  240. that.add(cb.cb, cb.ctx);
  241. });
  242. }
  243. });
  244. // Marionette Controller
  245. // ---------------------
  246. //
  247. // A multi-purpose object to use as a controller for
  248. // modules and routers, and as a mediator for workflow
  249. // and coordination of other objects, views, and more.
  250. Marionette.Controller = function(options){
  251. this.triggerMethod = Marionette.triggerMethod;
  252. this.options = options || {};
  253. Marionette.addEventBinder(this);
  254. if (_.isFunction(this.initialize)){
  255. this.initialize(this.options);
  256. }
  257. };
  258. Marionette.Controller.extend = Marionette.extend;
  259. // Controller Methods
  260. // --------------
  261. // Ensure it can trigger events with Backbone.Events
  262. _.extend(Marionette.Controller.prototype, Backbone.Events, {
  263. close: function(){
  264. this.unbindAll();
  265. this.triggerMethod("close");
  266. this.unbind();
  267. }
  268. });
  269. // Region
  270. // ------
  271. //
  272. // Manage the visual regions of your composite application. See
  273. // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
  274. Marionette.Region = function(options){
  275. this.options = options || {};
  276. Marionette.addEventBinder(this);
  277. this.el = Marionette.getOption(this, "el");
  278. if (!this.el){
  279. var err = new Error("An 'el' must be specified for a region.");
  280. err.name = "NoElError";
  281. throw err;
  282. }
  283. if (this.initialize){
  284. var args = Array.prototype.slice.apply(arguments);
  285. this.initialize.apply(this, args);
  286. }
  287. };
  288. // Region Type methods
  289. // -------------------
  290. _.extend(Marionette.Region, {
  291. // Build an instance of a region by passing in a configuration object
  292. // and a default region type to use if none is specified in the config.
  293. //
  294. // The config object should either be a string as a jQuery DOM selector,
  295. // a Region type directly, or an object literal that specifies both
  296. // a selector and regionType:
  297. //
  298. // ```js
  299. // {
  300. // selector: "#foo",
  301. // regionType: MyCustomRegion
  302. // }
  303. // ```
  304. //
  305. buildRegion: function(regionConfig, defaultRegionType){
  306. var regionIsString = (typeof regionConfig === "string");
  307. var regionSelectorIsString = (typeof regionConfig.selector === "string");
  308. var regionTypeIsUndefined = (typeof regionConfig.regionType === "undefined");
  309. var regionIsType = (typeof regionConfig === "function");
  310. if (!regionIsType && !regionIsString && !regionSelectorIsString) {
  311. throw new Error("Region must be specified as a Region type, a selector string or an object with selector property");
  312. }
  313. var selector, RegionType;
  314. // get the selector for the region
  315. if (regionIsString) {
  316. selector = regionConfig;
  317. }
  318. if (regionConfig.selector) {
  319. selector = regionConfig.selector;
  320. }
  321. // get the type for the region
  322. if (regionIsType){
  323. RegionType = regionConfig;
  324. }
  325. if (!regionIsType && regionTypeIsUndefined) {
  326. RegionType = defaultRegionType;
  327. }
  328. if (regionConfig.regionType) {
  329. RegionType = regionConfig.regionType;
  330. }
  331. // build the region instance
  332. var regionManager = new RegionType({
  333. el: selector
  334. });
  335. return regionManager;
  336. }
  337. });
  338. // Region Instance Methods
  339. // -----------------------
  340. _.extend(Marionette.Region.prototype, Backbone.Events, {
  341. // Displays a backbone view instance inside of the region.
  342. // Handles calling the `render` method for you. Reads content
  343. // directly from the `el` attribute. Also calls an optional
  344. // `onShow` and `close` method on your view, just after showing
  345. // or just before closing the view, respectively.
  346. show: function(view){
  347. this.ensureEl();
  348. this.close();
  349. view.render();
  350. this.open(view);
  351. Marionette.triggerMethod.call(view, "show");
  352. Marionette.triggerMethod.call(this, "show", view);
  353. this.currentView = view;
  354. },
  355. ensureEl: function(){
  356. if (!this.$el || this.$el.length === 0){
  357. this.$el = this.getEl(this.el);
  358. }
  359. },
  360. // Override this method to change how the region finds the
  361. // DOM element that it manages. Return a jQuery selector object.
  362. getEl: function(selector){
  363. return $(selector);
  364. },
  365. // Override this method to change how the new view is
  366. // appended to the `$el` that the region is managing
  367. open: function(view){
  368. this.$el.empty().append(view.el);
  369. },
  370. // Close the current view, if there is one. If there is no
  371. // current view, it does nothing and returns immediately.
  372. close: function(){
  373. var view = this.currentView;
  374. if (!view || view.isClosed){ return; }
  375. if (view.close) { view.close(); }
  376. Marionette.triggerMethod.call(this, "close");
  377. delete this.currentView;
  378. },
  379. // Attach an existing view to the region. This
  380. // will not call `render` or `onShow` for the new view,
  381. // and will not replace the current HTML for the `el`
  382. // of the region.
  383. attachView: function(view){
  384. this.currentView = view;
  385. },
  386. // Reset the region by closing any existing view and
  387. // clearing out the cached `$el`. The next time a view
  388. // is shown via this region, the region will re-query the
  389. // DOM for the region's `el`.
  390. reset: function(){
  391. this.close();
  392. delete this.$el;
  393. }
  394. });
  395. // Copy the `extend` function used by Backbone's classes
  396. Marionette.Region.extend = Marionette.extend;
  397. // Template Cache
  398. // --------------
  399. // Manage templates stored in `<script>` blocks,
  400. // caching them for faster access.
  401. Marionette.TemplateCache = function(templateId){
  402. this.templateId = templateId;
  403. };
  404. // TemplateCache object-level methods. Manage the template
  405. // caches from these method calls instead of creating
  406. // your own TemplateCache instances
  407. _.extend(Marionette.TemplateCache, {
  408. templateCaches: {},
  409. // Get the specified template by id. Either
  410. // retrieves the cached version, or loads it
  411. // from the DOM.
  412. get: function(templateId){
  413. var that = this;
  414. var cachedTemplate = this.templateCaches[templateId];
  415. if (!cachedTemplate){
  416. cachedTemplate = new Marionette.TemplateCache(templateId);
  417. this.templateCaches[templateId] = cachedTemplate;
  418. }
  419. return cachedTemplate.load();
  420. },
  421. // Clear templates from the cache. If no arguments
  422. // are specified, clears all templates:
  423. // `clear()`
  424. //
  425. // If arguments are specified, clears each of the
  426. // specified templates from the cache:
  427. // `clear("#t1", "#t2", "...")`
  428. clear: function(){
  429. var i;
  430. var args = Array.prototype.slice.apply(arguments);
  431. var length = args.length;
  432. if (length > 0){
  433. for(i=0; i<length; i++){
  434. delete this.templateCaches[args[i]];
  435. }
  436. } else {
  437. this.templateCaches = {};
  438. }
  439. }
  440. });
  441. // TemplateCache instance methods, allowing each
  442. // template cache object to manage it's own state
  443. // and know whether or not it has been loaded
  444. _.extend(Marionette.TemplateCache.prototype, {
  445. // Internal method to load the template asynchronously.
  446. load: function(){
  447. var that = this;
  448. // Guard clause to prevent loading this template more than once
  449. if (this.compiledTemplate){
  450. return this.compiledTemplate;
  451. }
  452. // Load the template and compile it
  453. var template = this.loadTemplate(this.templateId);
  454. this.compiledTemplate = this.compileTemplate(template);
  455. return this.compiledTemplate;
  456. },
  457. // Load a template from the DOM, by default. Override
  458. // this method to provide your own template retrieval,
  459. // such as asynchronous loading from a server.
  460. loadTemplate: function(templateId){
  461. var template = $(templateId).html();
  462. if (!template || template.length === 0){
  463. var msg = "Could not find template: '" + templateId + "'";
  464. var err = new Error(msg);
  465. err.name = "NoTemplateError";
  466. throw err;
  467. }
  468. return template;
  469. },
  470. // Pre-compile the template before caching it. Override
  471. // this method if you do not need to pre-compile a template
  472. // (JST / RequireJS for example) or if you want to change
  473. // the template engine used (Handebars, etc).
  474. compileTemplate: function(rawTemplate){
  475. return _.template(rawTemplate);
  476. }
  477. });
  478. // Renderer
  479. // --------
  480. // Render a template with data by passing in the template
  481. // selector and the data to render.
  482. Marionette.Renderer = {
  483. // Render a template with data. The `template` parameter is
  484. // passed to the `TemplateCache` object to retrieve the
  485. // template function. Override this method to provide your own
  486. // custom rendering and template handling for all of Marionette.
  487. render: function(template, data){
  488. var templateFunc = typeof template === 'function' ? template : Marionette.TemplateCache.get(template);
  489. var html = templateFunc(data);
  490. return html;
  491. }
  492. };
  493. // Marionette.View
  494. // ---------------
  495. // The core view type that other Marionette views extend from.
  496. Marionette.View = Backbone.View.extend({
  497. constructor: function(){
  498. _.bindAll(this, "render");
  499. Marionette.addEventBinder(this);
  500. var args = Array.prototype.slice.apply(arguments);
  501. Backbone.View.prototype.constructor.apply(this, args);
  502. Marionette.bindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
  503. Marionette.bindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
  504. Marionette.MonitorDOMRefresh(this);
  505. this.bindTo(this, "show", this.onShowCalled, this);
  506. },
  507. // import the "triggerMethod" to trigger events with corresponding
  508. // methods if the method exists
  509. triggerMethod: Marionette.triggerMethod,
  510. // Get the template for this view
  511. // instance. You can set a `template` attribute in the view
  512. // definition or pass a `template: "whatever"` parameter in
  513. // to the constructor options.
  514. getTemplate: function(){
  515. return Marionette.getOption(this, "template");
  516. },
  517. // Mix in template helper methods. Looks for a
  518. // `templateHelpers` attribute, which can either be an
  519. // object literal, or a function that returns an object
  520. // literal. All methods and attributes from this object
  521. // are copies to the object passed in.
  522. mixinTemplateHelpers: function(target){
  523. target = target || {};
  524. var templateHelpers = this.templateHelpers;
  525. if (_.isFunction(templateHelpers)){
  526. templateHelpers = templateHelpers.call(this);
  527. }
  528. return _.extend(target, templateHelpers);
  529. },
  530. // Configure `triggers` to forward DOM events to view
  531. // events. `triggers: {"click .foo": "do:foo"}`
  532. configureTriggers: function(){
  533. if (!this.triggers) { return; }
  534. var that = this;
  535. var triggerEvents = {};
  536. // Allow `triggers` to be configured as a function
  537. var triggers = _.result(this, "triggers");
  538. // Configure the triggers, prevent default
  539. // action and stop propagation of DOM events
  540. _.each(triggers, function(value, key){
  541. triggerEvents[key] = function(e){
  542. if (e && e.preventDefault){ e.preventDefault(); }
  543. if (e && e.stopPropagation){ e.stopPropagation(); }
  544. that.trigger(value);
  545. };
  546. });
  547. return triggerEvents;
  548. },
  549. // Overriding Backbone.View's delegateEvents specifically
  550. // to handle the `triggers` configuration
  551. delegateEvents: function(events){
  552. events = events || this.events;
  553. if (_.isFunction(events)){ events = events.call(this); }
  554. var combinedEvents = {};
  555. var triggers = this.configureTriggers();
  556. _.extend(combinedEvents, events, triggers);
  557. Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
  558. },
  559. // Internal method, handles the `show` event.
  560. onShowCalled: function(){},
  561. // Default `close` implementation, for removing a view from the
  562. // DOM and unbinding it. Regions will call this method
  563. // for you. You can specify an `onClose` method in your view to
  564. // add custom code that is called after the view is closed.
  565. close: function(){
  566. if (this.isClosed) { return; }
  567. // allow the close to be stopped by returning `false`
  568. // from the `onBeforeClose` method
  569. var shouldClose = this.triggerMethod("before:close");
  570. if (shouldClose === false){
  571. return;
  572. }
  573. // mark as closed before doing the actual close, to
  574. // prevent infinite loops within "close" event handlers
  575. // that are trying to close other views
  576. this.isClosed = true;
  577. this.remove();
  578. this.triggerMethod("close");
  579. this.unbindAll();
  580. },
  581. // This method binds the elements specified in the "ui" hash inside the view's code with
  582. // the associated jQuery selectors.
  583. bindUIElements: function(){
  584. if (!this.ui) { return; }
  585. var that = this;
  586. if (!this.uiBindings) {
  587. // We want to store the ui hash in uiBindings, since afterwards the values in the ui hash
  588. // will be overridden with jQuery selectors.
  589. this.uiBindings = this.ui;
  590. }
  591. // refreshing the associated selectors since they should point to the newly rendered elements.
  592. this.ui = {};
  593. _.each(_.keys(this.uiBindings), function(key) {
  594. var selector = that.uiBindings[key];
  595. that.ui[key] = that.$(selector);
  596. });
  597. }
  598. });
  599. // Item View
  600. // ---------
  601. // A single item view implementation that contains code for rendering
  602. // with underscore.js templates, serializing the view's model or collection,
  603. // and calling several methods on extended views, such as `onRender`.
  604. Marionette.ItemView = Marionette.View.extend({
  605. constructor: function(){
  606. var args = Array.prototype.slice.apply(arguments);
  607. Marionette.View.prototype.constructor.apply(this, args);
  608. if (this.initialEvents){
  609. this.initialEvents();
  610. }
  611. },
  612. // Serialize the model or collection for the view. If a model is
  613. // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
  614. // is also called, but is used to populate an `items` array in the
  615. // resulting data. If both are found, defaults to the model.
  616. // You can override the `serializeData` method in your own view
  617. // definition, to provide custom serialization for your view's data.
  618. serializeData: function(){
  619. var data = {};
  620. if (this.model) {
  621. data = this.model.toJSON();
  622. }
  623. else if (this.collection) {
  624. data = { items: this.collection.toJSON() };
  625. }
  626. return data;
  627. },
  628. // Render the view, defaulting to underscore.js templates.
  629. // You can override this in your view definition to provide
  630. // a very specific rendering for your view. In general, though,
  631. // you should override the `Marionette.Renderer` object to
  632. // change how Marionette renders views.
  633. render: function(){
  634. this.isClosed = false;
  635. this.triggerMethod("before:render", this);
  636. this.triggerMethod("item:before:render", this);
  637. var data = this.serializeData();
  638. data = this.mixinTemplateHelpers(data);
  639. var template = this.getTemplate();
  640. var html = Marionette.Renderer.render(template, data);
  641. this.$el.html(html);
  642. this.bindUIElements();
  643. this.triggerMethod("render", this);
  644. this.triggerMethod("item:rendered", this);
  645. return this;
  646. },
  647. // Override the default close event to add a few
  648. // more events that are triggered.
  649. close: function(){
  650. if (this.isClosed){ return; }
  651. this.triggerMethod('item:before:close');
  652. var args = Array.prototype.slice.apply(arguments);
  653. Marionette.View.prototype.close.apply(this, args);
  654. this.triggerMethod('item:closed');
  655. }
  656. });
  657. // Collection View
  658. // ---------------
  659. // A view that iterates over a Backbone.Collection
  660. // and renders an individual ItemView for each model.
  661. Marionette.CollectionView = Marionette.View.extend({
  662. // used as the prefix for item view events
  663. // that are forwarded through the collectionview
  664. itemViewEventPrefix: "itemview",
  665. // constructor
  666. constructor: function(options){
  667. this.initChildViewStorage();
  668. this.onShowCallbacks = new Marionette.Callbacks();
  669. var args = Array.prototype.slice.apply(arguments);
  670. Marionette.View.prototype.constructor.apply(this, args);
  671. this.initialEvents();
  672. },
  673. // Configured the initial events that the collection view
  674. // binds to. Override this method to prevent the initial
  675. // events, or to add your own initial events.
  676. initialEvents: function(){
  677. if (this.collection){
  678. this.bindTo(this.collection, "add", this.addChildView, this);
  679. this.bindTo(this.collection, "remove", this.removeItemView, this);
  680. this.bindTo(this.collection, "reset", this.render, this);
  681. }
  682. },
  683. // Handle a child item added to the collection
  684. addChildView: function(item, collection, options){
  685. this.closeEmptyView();
  686. var ItemView = this.getItemView(item);
  687. var index;
  688. if(options && options.index){
  689. index = options.index;
  690. } else {
  691. index = 0;
  692. }
  693. return this.addItemView(item, ItemView, index);
  694. },
  695. // Override from `Marionette.View` to guarantee the `onShow` method
  696. // of child views is called.
  697. onShowCalled: function(){
  698. this.onShowCallbacks.run();
  699. },
  700. // Internal method to trigger the before render callbacks
  701. // and events
  702. triggerBeforeRender: function(){
  703. this.triggerMethod("before:render", this);
  704. this.triggerMethod("collection:before:render", this);
  705. },
  706. // Internal method to trigger the rendered callbacks and
  707. // events
  708. triggerRendered: function(){
  709. this.triggerMethod("render", this);
  710. this.triggerMethod("collection:rendered", this);
  711. },
  712. // Render the collection of items. Override this method to
  713. // provide your own implementation of a render function for
  714. // the collection view.
  715. render: function(){
  716. this.isClosed = false;
  717. this.triggerBeforeRender();
  718. this.closeEmptyView();
  719. this.closeChildren();
  720. if (this.collection && this.collection.length > 0) {
  721. this.showCollection();
  722. } else {
  723. this.showEmptyView();
  724. }
  725. this.triggerRendered();
  726. return this;
  727. },
  728. // Internal method to loop through each item in the
  729. // collection view and show it
  730. showCollection: function(){
  731. var that = this;
  732. var ItemView;
  733. this.collection.each(function(item, index){
  734. ItemView = that.getItemView(item);
  735. that.addItemView(item, ItemView, index);
  736. });
  737. },
  738. // Internal method to show an empty view in place of
  739. // a collection of item views, when the collection is
  740. // empty
  741. showEmptyView: function(){
  742. var EmptyView = Marionette.getOption(this, "emptyView");
  743. if (EmptyView && !this._showingEmptyView){
  744. this._showingEmptyView = true;
  745. var model = new Backbone.Model();
  746. this.addItemView(model, EmptyView, 0);
  747. }
  748. },
  749. // Internal method to close an existing emptyView instance
  750. // if one exists. Called when a collection view has been
  751. // rendered empty, and then an item is added to the collection.
  752. closeEmptyView: function(){
  753. if (this._showingEmptyView){
  754. this.closeChildren();
  755. delete this._showingEmptyView;
  756. }
  757. },
  758. // Retrieve the itemView type, either from `this.options.itemView`
  759. // or from the `itemView` in the object definition. The "options"
  760. // takes precedence.
  761. getItemView: function(item){
  762. var itemView = Marionette.getOption(this, "itemView");
  763. if (!itemView){
  764. var err = new Error("An `itemView` must be specified");
  765. err.name = "NoItemViewError";
  766. throw err;
  767. }
  768. return itemView;
  769. },
  770. // Render the child item's view and add it to the
  771. // HTML for the collection view.
  772. addItemView: function(item, ItemView, index){
  773. var that = this;
  774. // get the itemViewOptions if any were specified
  775. var itemViewOptions = Marionette.getOption(this, "itemViewOptions");
  776. if (_.isFunction(itemViewOptions)){
  777. itemViewOptions = itemViewOptions.call(this, item);
  778. }
  779. // build the view
  780. var view = this.buildItemView(item, ItemView, itemViewOptions);
  781. // set up the child view event forwarding
  782. this.addChildViewEventForwarding(view);
  783. // this view is about to be added
  784. this.triggerMethod("before:item:added", view);
  785. // Store the child view itself so we can properly
  786. // remove and/or close it later
  787. this.children.add(view);
  788. // Render it and show it
  789. var renderResult = this.renderItemView(view, index);
  790. // this view was added
  791. this.triggerMethod("after:item:added", view);
  792. // call onShow for child item views
  793. if (view.onShow){
  794. this.onShowCallbacks.add(view.onShow, view);
  795. }
  796. return renderResult;
  797. },
  798. // Set up the child view event forwarding. Uses an "itemview:"
  799. // prefix in front of all forwarded events.
  800. addChildViewEventForwarding: function(view){
  801. var prefix = Marionette.getOption(this, "itemViewEventPrefix");
  802. // Forward all child item view events through the parent,
  803. // prepending "itemview:" to the event name
  804. var childBinding = this.bindTo(view, "all", function(){
  805. var args = slice.call(arguments);
  806. args[0] = prefix + ":" + args[0];
  807. args.splice(1, 0, view);
  808. this.triggerMethod.apply(this, args);
  809. }, this);
  810. // Store all child event bindings so we can unbind
  811. // them when removing / closing the child view
  812. this._childBindings = this._childBindings || {};
  813. this._childBindings[view.cid] = childBinding;
  814. },
  815. // render the item view
  816. renderItemView: function(view, index) {
  817. view.render();
  818. this.appendHtml(this, view, index);
  819. },
  820. // Build an `itemView` for every model in the collection.
  821. buildItemView: function(item, ItemViewType, itemViewOptions){
  822. var options = _.extend({model: item}, itemViewOptions);
  823. var view = new ItemViewType(options);
  824. return view;
  825. },
  826. // Remove the child view and close it
  827. removeItemView: function(item){
  828. var view = this.children.findByModel(item);
  829. if (view){
  830. var childBinding = this._childBindings[view.cid];
  831. if (childBinding) {
  832. this.unbindFrom(childBinding);
  833. delete this._childBindings[view.cid];
  834. }
  835. if (view.close){
  836. view.close();
  837. }
  838. this.children.remove(view);
  839. }
  840. if (!this.collection || this.collection.length === 0){
  841. this.showEmptyView();
  842. }
  843. this.triggerMethod("item:removed", view);
  844. },
  845. // Append the HTML to the collection's `el`.
  846. // Override this method to do something other
  847. // then `.append`.
  848. appendHtml: function(collectionView, itemView, index){
  849. collectionView.$el.append(itemView.el);
  850. },
  851. // Internal method to set up the `children` object for
  852. // storing all of the child views
  853. initChildViewStorage: function(){
  854. this.children = new Backbone.ChildViewContainer();
  855. },
  856. // Handle cleanup and other closing needs for
  857. // the collection of views.
  858. close: function(){
  859. if (this.isClosed){ return; }
  860. this.triggerMethod("collection:before:close");
  861. this.closeChildren();
  862. this.triggerMethod("collection:closed");
  863. var args = Array.prototype.slice.apply(arguments);
  864. Marionette.View.prototype.close.apply(this, args);
  865. },
  866. // Close the child views that this collection view
  867. // is holding on to, if any
  868. closeChildren: function(){
  869. var that = this;
  870. this.children.apply("close");
  871. // re-initialize to clean up after ourselves
  872. this.initChildViewStorage();
  873. }
  874. });
  875. // Composite View
  876. // --------------
  877. // Used for rendering a branch-leaf, hierarchical structure.
  878. // Extends directly from CollectionView and also renders an
  879. // an item view as `modelView`, for the top leaf
  880. Marionette.CompositeView = Marionette.CollectionView.extend({
  881. constructor: function(options){
  882. var args = Array.prototype.slice.apply(arguments);
  883. Marionette.CollectionView.apply(this, args);
  884. this.itemView = this.getItemView();
  885. },
  886. // Configured the initial events that the composite view
  887. // binds to. Override this method to prevent the initial
  888. // events, or to add your own initial events.
  889. initialEvents: function(){
  890. if (this.collection){
  891. this.bindTo(this.collection, "add", this.addChildView, this);
  892. this.bindTo(this.collection, "remove", this.removeItemView, this);
  893. this.bindTo(this.collection, "reset", this.renderCollection, this);
  894. }
  895. },
  896. // Retrieve the `itemView` to be used when rendering each of
  897. // the items in the collection. The default is to return
  898. // `this.itemView` or Marionette.CompositeView if no `itemView`
  899. // has been defined
  900. getItemView: function(item){
  901. var itemView = Marionette.getOption(this, "itemView") || this.constructor;
  902. if (!itemView){
  903. var err = new Error("An `itemView` must be specified");
  904. err.name = "NoItemViewError";
  905. throw err;
  906. }
  907. return itemView;
  908. },
  909. // Serialize the collection for the view.
  910. // You can override the `serializeData` method in your own view
  911. // definition, to provide custom serialization for your view's data.
  912. serializeData: function(){
  913. var data = {};
  914. if (this.model){
  915. data = this.model.toJSON();
  916. }
  917. return data;
  918. },
  919. // Renders the model once, and the collection once. Calling
  920. // this again will tell the model's view to re-render itself
  921. // but the collection will not re-render.
  922. render: function(){
  923. this.isClosed = false;
  924. this.resetItemViewContainer();
  925. var html = this.renderModel();
  926. this.$el.html(html);
  927. // the ui bindings is done here and not at the end of render since they
  928. // will not be available until after the model is rendered, but should be
  929. // available before the collection is rendered.
  930. this.bindUIElements();
  931. this.triggerMethod("composite:model:rendered");
  932. this.renderCollection();
  933. this.triggerMethod("composite:rendered");
  934. return this;
  935. },
  936. // Render the collection for the composite view
  937. renderCollection: function(){
  938. var args = Array.prototype.slice.apply(arguments);
  939. Marionette.CollectionView.prototype.render.apply(this, args);
  940. this.triggerMethod("composite:collection:rendered");
  941. },
  942. // Render an individual model, if we have one, as
  943. // part of a composite view (branch / leaf). For example:
  944. // a treeview.
  945. renderModel: function(){
  946. var data = {};
  947. data = this.serializeData();
  948. data = this.mixinTemplateHelpers(data);
  949. var template = this.getTemplate();
  950. return Marionette.Renderer.render(template, data);
  951. },
  952. // Appends the `el` of itemView instances to the specified
  953. // `itemViewContainer` (a jQuery selector). Override this method to
  954. // provide custom logic of how the child item view instances have their
  955. // HTML appended to the composite view instance.
  956. appendHtml: function(cv, iv){
  957. var $container = this.getItemViewContainer(cv);
  958. $container.append(iv.el);
  959. },
  960. // Internal method to ensure an `$itemViewContainer` exists, for the
  961. // `appendHtml` method to use.
  962. getItemViewContainer: function(containerView){
  963. if ("$itemViewContainer" in containerView){
  964. return containerView.$itemViewContainer;
  965. }
  966. var container;
  967. if (containerView.itemViewContainer){
  968. var selector = _.result(containerView, "itemViewContainer");
  969. container = containerView.$(selector);
  970. if (container.length <= 0) {
  971. var err = new Error("The specified `itemViewContainer` was not found: " + containerView.itemViewContainer);
  972. err.name = "ItemViewContainerMissingError";
  973. throw err;
  974. }
  975. } else {
  976. container = containerView.$el;
  977. }
  978. containerView.$itemViewContainer = container;
  979. return container;
  980. },
  981. // Internal method to reset the `$itemViewContainer` on render
  982. resetItemViewContainer: function(){
  983. if (this.$itemViewContainer){
  984. delete this.$itemViewContainer;
  985. }
  986. }
  987. });
  988. // Layout
  989. // ------
  990. // Used for managing application layouts, nested layouts and
  991. // multiple regions within an application or sub-application.
  992. //
  993. // A specialized view type that renders an area of HTML and then
  994. // attaches `Region` instances to the specified `regions`.
  995. // Used for composite view management and sub-application areas.
  996. Marionette.Layout = Marionette.ItemView.extend({
  997. regionType: Marionette.Region,
  998. // Ensure the regions are avialable when the `initialize` method
  999. // is called.
  1000. constructor: function () {
  1001. this._firstRender = true;
  1002. this.initializeRegions();
  1003. var args = Array.prototype.slice.apply(arguments);
  1004. Backbone.Marionette.ItemView.apply(this, args);
  1005. },
  1006. // Layout's render will use the existing region objects the
  1007. // first time it is called. Subsequent calls will close the
  1008. // views that the regions are showing and then reset the `el`
  1009. // for the regions to the newly rendered DOM elements.
  1010. render: function(){
  1011. if (this._firstRender){
  1012. // if this is the first render, don't do anything to
  1013. // reset the regions
  1014. this._firstRender = false;
  1015. } else {
  1016. // If this is not the first render call, then we need to
  1017. // re-initializing the `el` for each region
  1018. this.closeRegions();
  1019. this.reInitializeRegions();
  1020. }
  1021. var args = Array.prototype.slice.apply(arguments);
  1022. var result = Marionette.ItemView.prototype.render.apply(this, args);
  1023. return result;
  1024. },
  1025. // Handle closing regions, and then close the view itself.
  1026. close: function () {
  1027. if (this.isClosed){ return; }
  1028. this.closeRegions();
  1029. this.destroyRegions();
  1030. var args = Array.prototype.slice.apply(arguments);
  1031. Backbone.Marionette.ItemView.prototype.close.apply(this, args);
  1032. },
  1033. // Initialize the regions that have been defined in a
  1034. // `regions` attribute on this layout. The key of the
  1035. // hash becomes an attribute on the layout object directly.
  1036. // For example: `regions: { menu: ".menu-container" }`
  1037. // will product a `layout.menu` object which is a region
  1038. // that controls the `.menu-container` DOM element.
  1039. initializeRegions: function () {
  1040. if (!this.regionManagers){
  1041. this.regionManagers = {};
  1042. }
  1043. var that = this;
  1044. var regions = this.regions || {};
  1045. _.each(regions, function (region, name) {
  1046. var regionManager = Marionette.Region.buildRegion(region, that.regionType);
  1047. regionManager.getEl = function(selector){
  1048. return that.$(selector);
  1049. };
  1050. that.regionManagers[name] = regionManager;
  1051. that[name] = regionManager;
  1052. });
  1053. },
  1054. // Re-initialize all of the regions by updating the `el` that
  1055. // they point to
  1056. reInitializeRegions: function(){
  1057. if (this.regionManagers && _.size(this.regionManagers)===0){
  1058. this.initializeRegions();
  1059. } else {
  1060. _.each(this.regionManagers, function(region){
  1061. region.reset();
  1062. });
  1063. }
  1064. },
  1065. // Close all of the regions that have been opened by
  1066. // this layout. This method is called when the layout
  1067. // itself is closed.
  1068. closeRegions: function () {
  1069. var that = this;
  1070. _.each(this.regionManagers, function (manager, name) {
  1071. manager.close();
  1072. });
  1073. },
  1074. // Destroys all of the regions by removing references
  1075. // from the Layout
  1076. destroyRegions: function(){
  1077. var that = this;
  1078. _.each(this.regionManagers, function (manager, name) {
  1079. delete that[name];
  1080. });
  1081. this.regionManagers = {};
  1082. }
  1083. });
  1084. // AppRouter
  1085. // ---------
  1086. // Reduce the boilerplate code of handling route events
  1087. // and then calling a single method on another object.
  1088. // Have your routers configured to call the method on
  1089. // your object, directly.
  1090. //
  1091. // Configure an AppRouter with `appRoutes`.
  1092. //
  1093. // App routers can only take one `controller` object.
  1094. // It is recommended that you divide your controller
  1095. // objects in to smaller peices of related functionality
  1096. // and have multiple routers / controllers, instead of
  1097. // just one giant router and controller.
  1098. //
  1099. // You can also add standard routes to an AppRouter.
  1100. Marionette.AppRouter = Backbone.Router.extend({
  1101. constructor: function(options){
  1102. var args = Array.prototype.slice.apply(arguments);
  1103. Backbone.Router.prototype.constructor.apply(this, args);
  1104. this.options = options;
  1105. if (this.appRoutes){
  1106. var controller = Marionette.getOption(this, "controller");
  1107. this.processAppRoutes(controller, this.appRoutes);
  1108. }
  1109. },
  1110. // Internal method to process the `appRoutes` for the
  1111. // router, and turn them in to routes that trigger the
  1112. // specified method on the specified `controller`.
  1113. processAppRoutes: function(controller, appRoutes){
  1114. var method, methodName;
  1115. var route, routesLength, i;
  1116. var routes = [];
  1117. var router = this;
  1118. for(route in appRoutes){
  1119. if (appRoutes.hasOwnProperty(route)){
  1120. routes.unshift([route, appRoutes[route]]);
  1121. }
  1122. }
  1123. routesLength = routes.length;
  1124. for (i = 0; i < routesLength; i++){
  1125. route = routes[i][0];
  1126. methodName = routes[i][1];
  1127. method = controller[methodName];
  1128. if (!method){
  1129. var msg = "Method '" + methodName + "' was not found on the controller";
  1130. var err = new Error(msg);
  1131. err.name = "NoMethodError";
  1132. throw err;
  1133. }
  1134. method = _.bind(method, controller);
  1135. router.route(route, methodName, method);
  1136. }
  1137. }
  1138. });
  1139. // Application
  1140. // -----------
  1141. // Contain and manage the composite application as a whole.
  1142. // Stores and starts up `Region` objects, includes an
  1143. // event aggregator as `app.vent`
  1144. Marionette.Application = function(options){
  1145. this.initCallbacks = new Marionette.Callbacks();
  1146. this.vent = new Marionette.EventAggregator();
  1147. this.commands = new Backbone.Wreqr.Commands();
  1148. this.reqres = new Backbone.Wreqr.RequestResponse();
  1149. this.submodules = {};
  1150. _.extend(this, options);
  1151. Marionette.addEventBinder(this);
  1152. this.triggerMethod = Marionette.triggerMethod;
  1153. };
  1154. _.extend(Marionette.Application.prototype, Backbone.Events, {
  1155. // Command execution, facilitated by Backbone.Wreqr.Commands
  1156. execute: function(){
  1157. var args = Array.prototype.slice.apply(arguments);
  1158. this.commands.execute.apply(this.commands, args);
  1159. },
  1160. // Request/response, facilitated by Backbone.Wreqr.RequestResponse
  1161. request: function(){
  1162. var args = Array.prototype.slice.apply(arguments);
  1163. return this.reqres.request.apply(this.reqres, args);
  1164. },
  1165. // Add an initializer that is either run at when the `start`
  1166. // method is called, or run immediately if added after `start`
  1167. // has already been called.
  1168. addInitializer: function(initializer){
  1169. this.initCallbacks.add(initializer);
  1170. },
  1171. // kick off all of the application's processes.
  1172. // initializes all of the regions that have been added
  1173. // to the app, and runs all of the initializer functions
  1174. start: function(options){
  1175. this.triggerMethod("initialize:before", options);
  1176. this.initCallbacks.run(options, this);
  1177. this.triggerMethod("initialize:after", options);
  1178. this.triggerMethod("start", options);
  1179. },
  1180. // Add regions to your app.
  1181. // Accepts a hash of named strings or Region objects
  1182. // addRegions({something: "#someRegion"})
  1183. // addRegions{{something: Region.extend({el: "#someRegion"}) });
  1184. addRegions: function(regions){
  1185. var that = this;
  1186. _.each(regions, function (region, name) {
  1187. var regionManager = Marionette.Region.buildRegion(region, Marionette.Region);
  1188. that[name] = regionManager;
  1189. });
  1190. },
  1191. // Removes a region from your app.
  1192. // Accepts the regions name
  1193. // removeRegion('myRegion')
  1194. removeRegion: function(region) {
  1195. this[region].close();
  1196. delete this[region];
  1197. },
  1198. // Create a module, attached to the application
  1199. module: function(moduleNames, moduleDefinition){
  1200. // slice the args, and add this application object as the
  1201. // first argument of the array
  1202. var args = slice.call(arguments);
  1203. args.unshift(this);
  1204. // see the Marionette.Module object for more information
  1205. return Marionette.Module.create.apply(Marionette.Module, args);
  1206. }
  1207. });
  1208. // Copy the `extend` function used by Backbone's classes
  1209. Marionette.Application.extend = Marionette.extend;
  1210. // Module
  1211. // ------
  1212. // A simple module system, used to create privacy and encapsulation in
  1213. // Marionette applications
  1214. Marionette.Module = function(moduleName, app){
  1215. this.moduleName = moduleName;
  1216. // store sub-modules
  1217. this.submodules = {};
  1218. this._setupInitializersAndFinalizers();
  1219. // store the configuration for this module
  1220. this.config = {};
  1221. this.config.app = app;
  1222. // extend this module with an event binder
  1223. Marionette.addEventBinder(this);
  1224. this.triggerMethod = Marionette.triggerMethod;
  1225. };
  1226. // Extend the Module prototype with events / bindTo, so that the module
  1227. // can be used as an event aggregator or pub/sub.
  1228. _.extend(Marionette.Module.prototype, Backbone.Events, {
  1229. // Initializer for a specific module. Initializers are run when the
  1230. // module's `start` method is called.
  1231. addInitializer: function(callback){
  1232. this._initializerCallbacks.add(callback);
  1233. },
  1234. // Finalizers are run when a module is stopped. They are used to teardown
  1235. // and finalize any variables, references, events and other code that the
  1236. // module had set up.
  1237. addFinalizer: function(callback){
  1238. this._finalizerCallbacks.add(callback);
  1239. },
  1240. // Start the module, and run all of it's initializers
  1241. start: function(options){
  1242. // Prevent re-starting a module that is already started
  1243. if (this._isInitialized){ return; }
  1244. // start the sub-modules (depth-first hierarchy)
  1245. _.each(this.submodules, function(mod){
  1246. // check to see if we should start the sub-module with this parent
  1247. var startWithParent = true;
  1248. if (mod.config && mod.config.options){
  1249. startWithParent = mod.config.options.startWithParent;
  1250. }
  1251. // start the sub-module
  1252. if (startWithParent){
  1253. mod.start(options);
  1254. }
  1255. });
  1256. // run the callbacks to "start" the current module
  1257. this.triggerMethod("before:start", options);
  1258. this._initializerCallbacks.run(options, this);
  1259. this._isInitialized = true;
  1260. this.triggerMethod("start", options);
  1261. },
  1262. // Stop this module by running its finalizers and then stop all of
  1263. // the sub-modules for this module
  1264. stop: function(){
  1265. // if we are not initialized, don't bother finalizing
  1266. if (!this._isInitialized){ return; }
  1267. this._isInitialized = false;
  1268. Marionette.triggerMethod.call(this, "before:stop");
  1269. // stop the sub-modules; depth-first, to make sure the
  1270. // sub-modules are stopped / finalized before parents
  1271. _.each(this.submodules, function(mod){ mod.stop(); });
  1272. // run the finalizers
  1273. this._finalizerCallbacks.run();
  1274. // reset the initializers and finalizers
  1275. this._initializerCallbacks.reset();
  1276. this._finalizerCallbacks.reset();
  1277. Marionette.triggerMethod.call(this, "stop");
  1278. },
  1279. // Configure the module with a definition function and any custom args
  1280. // that are to be passed in to the definition function
  1281. addDefinition: function(moduleDefinition, customArgs){
  1282. this._runModuleDefinition(moduleDefinition, customArgs);
  1283. },
  1284. // Internal method: run the module definition function with the correct
  1285. // arguments
  1286. _runModuleDefinition: function(definition, customArgs){
  1287. if (!definition){ return; }
  1288. // build the correct list of arguments for the module definition
  1289. var args = _.flatten([
  1290. this,
  1291. this.config.app,
  1292. Backbone,
  1293. Marionette,
  1294. $, _,
  1295. customArgs
  1296. ]);
  1297. definition.apply(this, args);
  1298. },
  1299. // Internal method: set up new copies of initializers and finalizers.
  1300. // Calling this method will wipe out all existing initializers and
  1301. // finalizers.
  1302. _setupInitializersAndFinalizers: function(){
  1303. this._initializerCallbacks = new Marionette.Callbacks();
  1304. this._finalizerCallbacks = new Marionette.Callbacks();
  1305. }
  1306. });
  1307. // Function level methods to create modules
  1308. _.extend(Marionette.Module, {
  1309. // Create a module, hanging off the app parameter as the parent object.
  1310. create: function(app, moduleNames, moduleDefinition){
  1311. var that = this;
  1312. var parentModule = app;
  1313. moduleNames = moduleNames.split(".");
  1314. // get the custom args passed in after the module definition and
  1315. // get rid of the module name and definition function
  1316. var customArgs = slice.apply(arguments);
  1317. customArgs.splice(0, 3);
  1318. // Loop through all the parts of the module definition
  1319. var length = moduleNames.length;
  1320. _.each(moduleNames, function(moduleName, i){
  1321. var isLastModuleInChain = (i === length-1);
  1322. var isFirstModuleInChain = (i === 0);
  1323. var module = that._getModuleDefinition(parentModule, moduleName, app);
  1324. // if this is the last module in the chain, then set up
  1325. // all of the module options from the configuration
  1326. if (isLastModuleInChain){
  1327. module.config.options = that._getModuleOptions(module, parentModule, moduleDefinition);
  1328. // Only add a module definition and initializer when this is the last
  1329. // module in a "parent.child.grandchild" hierarchy of module names and
  1330. // when the module call has a definition function supplied
  1331. if (module.config.options.hasDefinition){
  1332. module.addDefinition(module.config.options.definition, customArgs);
  1333. }
  1334. }
  1335. // if it's a top level module, and this is the only
  1336. // module in the chain, then this one gets configured
  1337. // to start with the parent app.
  1338. if (isFirstModuleInChain && isLastModuleInChain ){
  1339. that._configureStartWithApp(app, module);
  1340. }
  1341. // Reset the parent module so that the next child
  1342. // in the list will be a…

Large files files are truncated, but you can click here to view the full file