PageRenderTime 71ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/scripts/backbone.marionette.js

https://github.com/ericbarnes/brunch
JavaScript | 2069 lines | 1077 code | 425 blank | 567 comment | 131 complexity | 8aba35a04be1a17945486c0e6cd51df5 MD5 | raw file

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

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

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