PageRenderTime 91ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 3ms

/public/js/myapp.js

https://github.com/robertd/benm
JavaScript | 15790 lines | 10428 code | 2454 blank | 2908 comment | 2832 complexity | 45d5867476733b3433bd7ccb65eee9e3 MD5 | raw file

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

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

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