PageRenderTime 65ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/bower_components/backbone.marionette/lib/core/backbone.marionette.js

https://github.com/robertd/benm
JavaScript | 2022 lines | 1080 code | 407 blank | 535 comment | 143 complexity | c9236effd491111cff0887535c30d98f MD5 | raw file

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

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

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