PageRenderTime 73ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://bitbucket.org/kolbyjAFK/cdnjs
JavaScript | 1787 lines | 934 code | 362 blank | 491 comment | 131 complexity | da124797fd0f25f4c64495d22a9e272a MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception

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

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

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