PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/ajax/libs/backbone.marionette/0.9.3/backbone.marionette.js

https://gitlab.com/Blueprint-Marketing/cdnjs
JavaScript | 1148 lines | 620 code | 207 blank | 321 comment | 84 complexity | 02031051dbe1dddcf7fae686f86e02bf MD5 | raw file
  1. // Backbone.Marionette v0.9.3
  2. //
  3. // Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
  4. // Distributed Under MIT License
  5. //
  6. // Documentation and Full License Available at:
  7. // http://github.com/derickbailey/backbone.marionette
  8. Backbone.Marionette = (function(Backbone, _, $){
  9. var Marionette = {};
  10. // BindTo: Event Binding
  11. // ---------------------
  12. // BindTo facilitates the binding and unbinding of events
  13. // from objects that extend `Backbone.Events`. It makes
  14. // unbinding events, even with anonymous callback functions,
  15. // easy.
  16. //
  17. // Thanks to Johnny Oshika for this code.
  18. // http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
  19. Marionette.BindTo = {
  20. // Store the event binding in array so it can be unbound
  21. // easily, at a later point in time.
  22. bindTo: function (obj, eventName, callback, context) {
  23. context = context || this;
  24. obj.on(eventName, callback, context);
  25. if (!this.bindings) { this.bindings = []; }
  26. var binding = {
  27. obj: obj,
  28. eventName: eventName,
  29. callback: callback,
  30. context: context
  31. }
  32. this.bindings.push(binding);
  33. return binding;
  34. },
  35. // Unbind from a single binding object. Binding objects are
  36. // returned from the `bindTo` method call.
  37. unbindFrom: function(binding){
  38. binding.obj.off(binding.eventName, binding.callback, binding.context);
  39. this.bindings = _.reject(this.bindings, function(bind){return bind === binding});
  40. },
  41. // Unbind all of the events that we have stored.
  42. unbindAll: function () {
  43. var that = this;
  44. // The `unbindFrom` call removes elements from the array
  45. // while it is being iterated, so clone it first.
  46. var bindings = _.map(this.bindings, _.identity);
  47. _.each(bindings, function (binding, index) {
  48. that.unbindFrom(binding);
  49. });
  50. }
  51. };
  52. // Marionette.View
  53. // ---------------
  54. // The core view type that other Marionette views extend from.
  55. Marionette.View = Backbone.View.extend({
  56. constructor: function(){
  57. Backbone.View.prototype.constructor.apply(this, arguments);
  58. this.bindTo(this, "show", this.onShowCalled, this);
  59. },
  60. // Get the template for this view
  61. // instance. You can set a `template` attribute in the view
  62. // definition or pass a `template: "whatever"` parameter in
  63. // to the constructor options.
  64. getTemplate: function(){
  65. var template;
  66. // Get the template from `this.options.template` or
  67. // `this.template`. The `options` takes precedence.
  68. if (this.options && this.options.template){
  69. template = this.options.template;
  70. } else {
  71. template = this.template;
  72. }
  73. return template;
  74. },
  75. // Serialize the model or collection for the view. If a model is
  76. // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
  77. // is also called, but is used to populate an `items` array in the
  78. // resulting data. If both are found, defaults to the model.
  79. // You can override the `serializeData` method in your own view
  80. // definition, to provide custom serialization for your view's data.
  81. serializeData: function(){
  82. var data;
  83. if (this.model) {
  84. data = this.model.toJSON();
  85. }
  86. else if (this.collection) {
  87. data = { items: this.collection.toJSON() };
  88. }
  89. data = this.mixinTemplateHelpers(data);
  90. return data;
  91. },
  92. // Mix in template helper methods. Looks for a
  93. // `templateHelpers` attribute, which can either be an
  94. // object literal, or a function that returns an object
  95. // literal. All methods and attributes from this object
  96. // are copies to the object passed in.
  97. mixinTemplateHelpers: function(target){
  98. target = target || {};
  99. var templateHelpers = this.templateHelpers;
  100. if (_.isFunction(templateHelpers)){
  101. templateHelpers = templateHelpers.call(this);
  102. }
  103. return _.extend(target, templateHelpers);
  104. },
  105. // Configure `triggers` to forward DOM events to view
  106. // events. `triggers: {"click .foo": "do:foo"}`
  107. configureTriggers: function(){
  108. if (!this.triggers) { return; }
  109. var triggers = this.triggers;
  110. var that = this;
  111. var triggerEvents = {};
  112. // Allow `triggers` to be configured as a function
  113. if (_.isFunction(triggers)){ triggers = triggers.call(this); }
  114. // Configure the triggers, prevent default
  115. // action and stop propagation of DOM events
  116. _.each(triggers, function(value, key){
  117. triggerEvents[key] = function(e){
  118. if (e && e.preventDefault){ e.preventDefault(); }
  119. if (e && e.stopPropagation){ e.stopPropagation(); }
  120. that.trigger(value);
  121. }
  122. });
  123. return triggerEvents;
  124. },
  125. // Overriding Backbone.View's delegateEvents specifically
  126. // to handle the `triggers` configuration
  127. delegateEvents: function(events){
  128. events = events || this.events;
  129. if (_.isFunction(events)){ events = events.call(this)}
  130. var combinedEvents = {};
  131. var triggers = this.configureTriggers();
  132. _.extend(combinedEvents, events, triggers);
  133. Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
  134. },
  135. // Internal method, handles the `show` event.
  136. onShowCalled: function(){},
  137. // Default `close` implementation, for removing a view from the
  138. // DOM and unbinding it. Regions will call this method
  139. // for you. You can specify an `onClose` method in your view to
  140. // add custom code that is called after the view is closed.
  141. close: function(){
  142. if (this.beforeClose) { this.beforeClose(); }
  143. this.remove();
  144. if (this.onClose) { this.onClose(); }
  145. this.trigger('close');
  146. this.unbindAll();
  147. this.unbind();
  148. }
  149. });
  150. // Copy the features of `BindTo`
  151. _.extend(Marionette.View.prototype, Marionette.BindTo);
  152. // Item View
  153. // ---------
  154. // A single item view implementation that contains code for rendering
  155. // with underscore.js templates, serializing the view's model or collection,
  156. // and calling several methods on extended views, such as `onRender`.
  157. Marionette.ItemView = Marionette.View.extend({
  158. constructor: function(){
  159. Marionette.View.prototype.constructor.apply(this, arguments);
  160. this.initialEvents();
  161. },
  162. // Configured the initial events that the item view
  163. // binds to. Override this method to prevent the initial
  164. // events, or to add your own initial events.
  165. initialEvents: function(){
  166. if (this.collection){
  167. this.bindTo(this.collection, "reset", this.render, this);
  168. }
  169. },
  170. // Render the view, defaulting to underscore.js templates.
  171. // You can override this in your view definition to provide
  172. // a very specific rendering for your view. In general, though,
  173. // you should override the `Marionette.Renderer` object to
  174. // change how Marionette renders views.
  175. render: function(){
  176. if (this.beforeRender){ this.beforeRender(); }
  177. this.trigger("before:render", this);
  178. this.trigger("item:before:render", this);
  179. var data = this.serializeData();
  180. var template = this.getTemplate();
  181. var html = Marionette.Renderer.render(template, data);
  182. this.$el.html(html);
  183. if (this.onRender){ this.onRender(); }
  184. this.trigger("render", this);
  185. this.trigger("item:rendered", this);
  186. return this;
  187. },
  188. // Override the default close event to add a few
  189. // more events that are triggered.
  190. close: function(){
  191. this.trigger('item:before:close');
  192. Marionette.View.prototype.close.apply(this, arguments);
  193. this.trigger('item:closed');
  194. }
  195. });
  196. // Collection View
  197. // ---------------
  198. // A view that iterates over a Backbone.Collection
  199. // and renders an individual ItemView for each model.
  200. Marionette.CollectionView = Marionette.View.extend({
  201. constructor: function(){
  202. Marionette.View.prototype.constructor.apply(this, arguments);
  203. this.initChildViewStorage();
  204. this.initialEvents();
  205. this.onShowCallbacks = new Marionette.Callbacks();
  206. },
  207. // Configured the initial events that the collection view
  208. // binds to. Override this method to prevent the initial
  209. // events, or to add your own initial events.
  210. initialEvents: function(){
  211. if (this.collection){
  212. this.bindTo(this.collection, "add", this.addChildView, this);
  213. this.bindTo(this.collection, "remove", this.removeItemView, this);
  214. this.bindTo(this.collection, "reset", this.render, this);
  215. }
  216. },
  217. // Handle a child item added to the collection
  218. addChildView: function(item, collection, options){
  219. this.closeEmptyView();
  220. var ItemView = this.getItemView();
  221. return this.addItemView(item, ItemView, options.index);
  222. },
  223. // Override from `Marionette.View` to guarantee the `onShow` method
  224. // of child views is called.
  225. onShowCalled: function(){
  226. this.onShowCallbacks.run();
  227. },
  228. // Internal method to trigger the before render callbacks
  229. // and events
  230. triggerBeforeRender: function(){
  231. if (this.beforeRender) { this.beforeRender(); }
  232. this.trigger("before:render", this);
  233. this.trigger("collection:before:render", this);
  234. },
  235. // Internal method to trigger the rendered callbacks and
  236. // events
  237. triggerRendered: function(){
  238. if (this.onRender) { this.onRender(); }
  239. this.trigger("render", this);
  240. this.trigger("collection:rendered", this);
  241. },
  242. // Render the collection of items. Override this method to
  243. // provide your own implementation of a render function for
  244. // the collection view.
  245. render: function(){
  246. this.triggerBeforeRender();
  247. this.closeChildren();
  248. if (this.collection && this.collection.length > 0) {
  249. this.showCollection();
  250. } else {
  251. this.showEmptyView();
  252. }
  253. this.triggerRendered();
  254. return this;
  255. },
  256. // Internal method to loop through each item in the
  257. // collection view and show it
  258. showCollection: function(){
  259. var that = this;
  260. var ItemView = this.getItemView();
  261. this.collection.each(function(item, index){
  262. that.addItemView(item, ItemView, index);
  263. });
  264. },
  265. // Internal method to show an empty view in place of
  266. // a collection of item views, when the collection is
  267. // empty
  268. showEmptyView: function(){
  269. var EmptyView = this.options.emptyView || this.emptyView;
  270. if (EmptyView){
  271. this.showingEmptyView = true;
  272. var model = new Backbone.Model();
  273. this.addItemView(model, EmptyView, 0);
  274. }
  275. },
  276. // Internal method to close an existing emptyView instance
  277. // if one exists. Called when a collection view has been
  278. // rendered empty, and then an item is added to the collection.
  279. closeEmptyView: function(){
  280. if (this.showingEmptyView){
  281. this.closeChildren();
  282. delete this.showingEmptyView;
  283. }
  284. },
  285. // Retrieve the itemView type, either from `this.options.itemView`
  286. // or from the `itemView` in the object definition. The "options"
  287. // takes precedence.
  288. getItemView: function(){
  289. var itemView = this.options.itemView || this.itemView;
  290. if (!itemView){
  291. var err = new Error("An `itemView` must be specified");
  292. err.name = "NoItemViewError";
  293. throw err;
  294. }
  295. return itemView;
  296. },
  297. // Render the child item's view and add it to the
  298. // HTML for the collection view.
  299. addItemView: function(item, ItemView, index){
  300. var that = this;
  301. var view = this.buildItemView(item, ItemView);
  302. // Store the child view itself so we can properly
  303. // remove and/or close it later
  304. this.storeChild(view);
  305. if (this.onItemAdded){ this.onItemAdded(view); }
  306. this.trigger("item:added", view);
  307. // Render it and show it
  308. var renderResult = this.renderItemView(view, index);
  309. // call onShow for child item views
  310. if (view.onShow){
  311. this.onShowCallbacks.add(view.onShow, view);
  312. }
  313. // Forward all child item view events through the parent,
  314. // prepending "itemview:" to the event name
  315. var childBinding = this.bindTo(view, "all", function(){
  316. var args = slice.call(arguments);
  317. args[0] = "itemview:" + args[0];
  318. args.splice(1, 0, view);
  319. that.trigger.apply(that, args);
  320. });
  321. // Store all child event bindings so we can unbind
  322. // them when removing / closing the child view
  323. this.childBindings = this.childBindings || {};
  324. this.childBindings[view.cid] = childBinding;
  325. return renderResult;
  326. },
  327. // render the item view
  328. renderItemView: function(view, index) {
  329. view.render();
  330. this.appendHtml(this, view, index);
  331. },
  332. // Build an `itemView` for every model in the collection.
  333. buildItemView: function(item, ItemView){
  334. var itemViewOptions = _.result(this, "itemViewOptions");
  335. var options = _.extend({model: item}, itemViewOptions);
  336. var view = new ItemView(options);
  337. return view;
  338. },
  339. // Remove the child view and close it
  340. removeItemView: function(item){
  341. var view = this.children[item.cid];
  342. if (view){
  343. var childBinding = this.childBindings[view.cid];
  344. if (childBinding) {
  345. this.unbindFrom(childBinding);
  346. delete this.childBindings[view.cid];
  347. }
  348. view.close();
  349. delete this.children[item.cid];
  350. }
  351. if (this.collection.length === 0){
  352. this.showEmptyView();
  353. }
  354. this.trigger("item:removed", view);
  355. },
  356. // Append the HTML to the collection's `el`.
  357. // Override this method to do something other
  358. // then `.append`.
  359. appendHtml: function(collectionView, itemView, index){
  360. collectionView.$el.append(itemView.el);
  361. },
  362. // Store references to all of the child `itemView`
  363. // instances so they can be managed and cleaned up, later.
  364. storeChild: function(view){
  365. this.children[view.model.cid] = view;
  366. },
  367. // Internal method to set up the `children` object for
  368. // storing all of the child views
  369. initChildViewStorage: function(){
  370. this.children = {};
  371. },
  372. // Handle cleanup and other closing needs for
  373. // the collection of views.
  374. close: function(){
  375. this.trigger("collection:before:close");
  376. this.closeChildren();
  377. Marionette.View.prototype.close.apply(this, arguments);
  378. this.trigger("collection:closed");
  379. },
  380. // Close the child views that this collection view
  381. // is holding on to, if any
  382. closeChildren: function(){
  383. var that = this;
  384. if (this.children){
  385. _.each(_.clone(this.children), function(childView){
  386. that.removeItemView(childView.model);
  387. });
  388. }
  389. }
  390. });
  391. // Composite View
  392. // --------------
  393. // Used for rendering a branch-leaf, hierarchical structure.
  394. // Extends directly from CollectionView and also renders an
  395. // an item view as `modelView`, for the top leaf
  396. Marionette.CompositeView = Marionette.CollectionView.extend({
  397. constructor: function(options){
  398. Marionette.CollectionView.apply(this, arguments);
  399. this.itemView = this.getItemView();
  400. },
  401. // Configured the initial events that the composite view
  402. // binds to. Override this method to prevent the initial
  403. // events, or to add your own initial events.
  404. initialEvents: function(){
  405. if (this.collection){
  406. this.bindTo(this.collection, "add", this.addChildView, this);
  407. this.bindTo(this.collection, "remove", this.removeItemView, this);
  408. this.bindTo(this.collection, "reset", this.renderCollection, this);
  409. }
  410. },
  411. // Retrieve the `itemView` to be used when rendering each of
  412. // the items in the collection. The default is to return
  413. // `this.itemView` or Marionette.CompositeView if no `itemView`
  414. // has been defined
  415. getItemView: function(){
  416. return this.itemView || this.constructor;
  417. },
  418. // Renders the model once, and the collection once. Calling
  419. // this again will tell the model's view to re-render itself
  420. // but the collection will not re-render.
  421. render: function(){
  422. var that = this;
  423. this.resetItemViewContainer();
  424. var html = this.renderModel();
  425. this.$el.html(html);
  426. this.trigger("composite:model:rendered");
  427. this.trigger("render");
  428. this.renderCollection();
  429. this.trigger("composite:rendered");
  430. return this;
  431. },
  432. // Render the collection for the composite view
  433. renderCollection: function(){
  434. Marionette.CollectionView.prototype.render.apply(this, arguments);
  435. this.trigger("composite:collection:rendered");
  436. },
  437. // Render an individual model, if we have one, as
  438. // part of a composite view (branch / leaf). For example:
  439. // a treeview.
  440. renderModel: function(){
  441. var data = {};
  442. data = this.serializeData();
  443. var template = this.getTemplate();
  444. return Marionette.Renderer.render(template, data);
  445. },
  446. // Appends the `el` of itemView instances to the specified
  447. // `itemViewContainer` (a jQuery selector). Override this method to
  448. // provide custom logic of how the child item view instances have their
  449. // HTML appended to the composite view instance.
  450. appendHtml: function(cv, iv){
  451. var $container = this.getItemViewContainer(cv);
  452. $container.append(iv.el);
  453. },
  454. // Internal method to ensure an `$itemViewContainer` exists, for the
  455. // `appendHtml` method to use.
  456. getItemViewContainer: function(containerView){
  457. var container;
  458. if ("$itemViewContainer" in containerView){
  459. container = containerView.$itemViewContainer;
  460. } else {
  461. if (containerView.itemViewContainer){
  462. container = containerView.$(_.result(containerView, "itemViewContainer"));
  463. } else {
  464. container = containerView.$el;
  465. }
  466. containerView.$itemViewContainer = container;
  467. }
  468. return container;
  469. },
  470. // Internal method to reset the `$itemViewContainer` on render
  471. resetItemViewContainer: function(){
  472. if (this.$itemViewContainer){
  473. delete this.$itemViewContainer;
  474. }
  475. }
  476. });
  477. // Region
  478. // ------
  479. // Manage the visual regions of your composite application. See
  480. // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
  481. Marionette.Region = function(options){
  482. this.options = options || {};
  483. _.extend(this, options);
  484. if (!this.el){
  485. var err = new Error("An 'el' must be specified");
  486. err.name = "NoElError";
  487. throw err;
  488. }
  489. if (this.initialize){
  490. this.initialize.apply(this, arguments);
  491. }
  492. };
  493. _.extend(Marionette.Region.prototype, Backbone.Events, {
  494. // Displays a backbone view instance inside of the region.
  495. // Handles calling the `render` method for you. Reads content
  496. // directly from the `el` attribute. Also calls an optional
  497. // `onShow` and `close` method on your view, just after showing
  498. // or just before closing the view, respectively.
  499. show: function(view){
  500. var that = this;
  501. this.ensureEl();
  502. this.close();
  503. view.render();
  504. this.open(view);
  505. if (view.onShow) { view.onShow(); }
  506. view.trigger("show");
  507. if (this.onShow) { this.onShow(view); }
  508. this.trigger("view:show", view);
  509. this.currentView = view;
  510. },
  511. ensureEl: function(){
  512. if (!this.$el || this.$el.length === 0){
  513. this.$el = this.getEl(this.el);
  514. }
  515. },
  516. // Override this method to change how the region finds the
  517. // DOM element that it manages. Return a jQuery selector object.
  518. getEl: function(selector){
  519. return $(selector);
  520. },
  521. // Override this method to change how the new view is
  522. // appended to the `$el` that the region is managing
  523. open: function(view){
  524. this.$el.html(view.el);
  525. },
  526. // Close the current view, if there is one. If there is no
  527. // current view, it does nothing and returns immediately.
  528. close: function(){
  529. var view = this.currentView;
  530. if (!view){ return; }
  531. if (view.close) { view.close(); }
  532. this.trigger("view:closed", view);
  533. delete this.currentView;
  534. },
  535. // Attach an existing view to the region. This
  536. // will not call `render` or `onShow` for the new view,
  537. // and will not replace the current HTML for the `el`
  538. // of the region.
  539. attachView: function(view){
  540. this.currentView = view;
  541. }
  542. });
  543. // Copy the `extend` function used by Backbone's classes
  544. Marionette.Region.extend = Backbone.View.extend;
  545. // Copy the features of `BindTo`
  546. _.extend(Marionette.Region.prototype, Marionette.BindTo);
  547. // Layout
  548. // ------
  549. // Used for managing application layouts, nested layouts and
  550. // multiple regions within an application or sub-application.
  551. //
  552. // A specialized view type that renders an area of HTML and then
  553. // attaches `Region` instances to the specified `regions`.
  554. // Used for composite view management and sub-application areas.
  555. Marionette.Layout = Marionette.ItemView.extend({
  556. constructor: function () {
  557. Backbone.Marionette.ItemView.apply(this, arguments);
  558. this.initializeRegions();
  559. },
  560. // Layout's render will use the existing region objects the
  561. // first time it is called. Subsequent calls will close the
  562. // views that the regions are showing and then reset the `el`
  563. // for the regions to the newly rendered DOM elements.
  564. render: function(){
  565. var result = Marionette.ItemView.prototype.render.apply(this, arguments);
  566. // Rewrite this function to handle re-rendering and
  567. // re-initializing the `el` for each region
  568. this.render = function(){
  569. this.closeRegions();
  570. this.reInitializeRegions();
  571. var result = Marionette.ItemView.prototype.render.apply(this, arguments);
  572. return result;
  573. }
  574. return result;
  575. },
  576. // Handle closing regions, and then close the view itself.
  577. close: function () {
  578. this.closeRegions();
  579. this.destroyRegions();
  580. Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
  581. },
  582. // Initialize the regions that have been defined in a
  583. // `regions` attribute on this layout. The key of the
  584. // hash becomes an attribute on the layout object directly.
  585. // For example: `regions: { menu: ".menu-container" }`
  586. // will product a `layout.menu` object which is a region
  587. // that controls the `.menu-container` DOM element.
  588. initializeRegions: function () {
  589. if (!this.regionManagers){
  590. this.regionManagers = {};
  591. }
  592. var that = this;
  593. _.each(this.regions, function (selector, name) {
  594. var regionManager = new Backbone.Marionette.Region({
  595. el: selector,
  596. getEl: function(selector){
  597. return that.$(selector);
  598. }
  599. });
  600. that.regionManagers[name] = regionManager;
  601. that[name] = regionManager;
  602. });
  603. },
  604. // Re-initialize all of the regions by updating the `el` that
  605. // they point to
  606. reInitializeRegions: function(){
  607. _.each(this.regionManagers, function(region){
  608. delete region.$el;
  609. });
  610. },
  611. // Close all of the regions that have been opened by
  612. // this layout. This method is called when the layout
  613. // itself is closed.
  614. closeRegions: function () {
  615. var that = this;
  616. _.each(this.regionManagers, function (manager, name) {
  617. manager.close();
  618. });
  619. },
  620. // Destroys all of the regions by removing references
  621. // from the Layout
  622. destroyRegions: function(){
  623. var that = this;
  624. _.each(this.regionManagers, function (manager, name) {
  625. delete that[name];
  626. });
  627. this.regionManagers = {};
  628. }
  629. });
  630. // Application
  631. // -----------
  632. // Contain and manage the composite application as a whole.
  633. // Stores and starts up `Region` objects, includes an
  634. // event aggregator as `app.vent`
  635. Marionette.Application = function(options){
  636. this.initCallbacks = new Marionette.Callbacks();
  637. this.vent = new Marionette.EventAggregator();
  638. _.extend(this, options);
  639. };
  640. _.extend(Marionette.Application.prototype, Backbone.Events, {
  641. // Add an initializer that is either run at when the `start`
  642. // method is called, or run immediately if added after `start`
  643. // has already been called.
  644. addInitializer: function(initializer){
  645. this.initCallbacks.add(initializer);
  646. },
  647. // kick off all of the application's processes.
  648. // initializes all of the regions that have been added
  649. // to the app, and runs all of the initializer functions
  650. start: function(options){
  651. this.trigger("initialize:before", options);
  652. this.initCallbacks.run(options, this);
  653. this.trigger("initialize:after", options);
  654. this.trigger("start", options);
  655. },
  656. // Add regions to your app.
  657. // Accepts a hash of named strings or Region objects
  658. // addRegions({something: "#someRegion"})
  659. // addRegions{{something: Region.extend({el: "#someRegion"}) });
  660. addRegions: function(regions){
  661. var regionValue, regionObj, region;
  662. for(region in regions){
  663. if (regions.hasOwnProperty(region)){
  664. regionValue = regions[region];
  665. if (typeof regionValue === "string"){
  666. regionObj = new Marionette.Region({
  667. el: regionValue
  668. });
  669. } else {
  670. regionObj = new regionValue();
  671. }
  672. this[region] = regionObj;
  673. }
  674. }
  675. },
  676. // Create a module, attached to the application
  677. module: function(){
  678. // see the Marionette.Module object for more information
  679. return Marionette.Module.create.apply(this, arguments);
  680. }
  681. });
  682. // Copy the `extend` function used by Backbone's classes
  683. Marionette.Application.extend = Backbone.View.extend;
  684. // Copy the features of `BindTo`
  685. _.extend(Marionette.Application.prototype, Marionette.BindTo);
  686. // AppRouter
  687. // ---------
  688. // Reduce the boilerplate code of handling route events
  689. // and then calling a single method on another object.
  690. // Have your routers configured to call the method on
  691. // your object, directly.
  692. //
  693. // Configure an AppRouter with `appRoutes`.
  694. //
  695. // App routers can only take one `controller` object.
  696. // It is recommended that you divide your controller
  697. // objects in to smaller peices of related functionality
  698. // and have multiple routers / controllers, instead of
  699. // just one giant router and controller.
  700. //
  701. // You can also add standard routes to an AppRouter.
  702. Marionette.AppRouter = Backbone.Router.extend({
  703. constructor: function(options){
  704. Backbone.Router.prototype.constructor.call(this, options);
  705. if (this.appRoutes){
  706. var controller = this.controller;
  707. if (options && options.controller) {
  708. controller = options.controller;
  709. }
  710. this.processAppRoutes(controller, this.appRoutes);
  711. }
  712. },
  713. // Internal method to process the `appRoutes` for the
  714. // router, and turn them in to routes that trigger the
  715. // specified method on the specified `controller`.
  716. processAppRoutes: function(controller, appRoutes){
  717. var method, methodName;
  718. var route, routesLength, i;
  719. var routes = [];
  720. var router = this;
  721. for(route in appRoutes){
  722. if (appRoutes.hasOwnProperty(route)){
  723. routes.unshift([route, appRoutes[route]]);
  724. }
  725. }
  726. routesLength = routes.length;
  727. for (i = 0; i < routesLength; i++){
  728. route = routes[i][0];
  729. methodName = routes[i][1];
  730. method = controller[methodName];
  731. if (!method){
  732. var msg = "Method '" + methodName + "' was not found on the controller";
  733. var err = new Error(msg);
  734. err.name = "NoMethodError";
  735. throw err;
  736. }
  737. method = _.bind(method, controller);
  738. router.route(route, methodName, method);
  739. }
  740. }
  741. });
  742. // Module
  743. // ------
  744. // A simple module system, used to create privacy and encapsulation in
  745. // Marionette applications
  746. Marionette.Module = function(){};
  747. // Extend the Module prototype with events / bindTo, so that the module
  748. // can be used as an event aggregator or pub/sub.
  749. _.extend(Marionette.Module.prototype, Backbone.Events, Marionette.BindTo);
  750. // Function level methods to create modules
  751. _.extend(Marionette.Module, {
  752. // Create a module, hanging off 'this' as the parent object. This
  753. // method must be called with .apply or .create
  754. create: function(moduleNames, moduleDefinition){
  755. var moduleName, module, moduleOverride;
  756. var parentObject = this;
  757. var parentModule = this;
  758. var moduleNames = moduleNames.split(".");
  759. // Loop through all the parts of the module definition
  760. var length = moduleNames.length;
  761. for(var i = 0; i < length; i++){
  762. var isLastModuleInChain = (i === length-1);
  763. // Get the module name, and check if it exists on
  764. // the current parent already
  765. moduleName = moduleNames[i];
  766. module = parentModule[moduleName];
  767. // Create a new module if we don't have one already
  768. if (!module){
  769. module = new Marionette.Module();
  770. }
  771. // Check to see if we need to run the definition
  772. // for the module. Only run the definition if one
  773. // is supplied, and if we're at the last segment
  774. // of the "Module.Name" chain.
  775. if (isLastModuleInChain && moduleDefinition){
  776. // get the custom args passed in after the module definition and
  777. // get rid of the module name and definition function
  778. var customArgs = slice.apply(arguments);
  779. customArgs.shift();
  780. customArgs.shift();
  781. // final arguments list for the module definition
  782. var argsArray = [module, parentObject, Backbone, Marionette, jQuery, _, customArgs];
  783. // flatten the nested array
  784. var args = _.flatten(argsArray);
  785. // ensure the module definition's `this` is the module itself
  786. moduleDefinition.apply(module, args);
  787. }
  788. // If the defined module is not what we are
  789. // currently storing as the module, replace it
  790. if (parentModule[moduleName] !== module){
  791. parentModule[moduleName] = module;
  792. }
  793. // Reset the parent module so that the next child
  794. // in the list will be added to the correct parent
  795. parentModule = module;
  796. }
  797. // Return the last module in the definition chain
  798. return module;
  799. }
  800. });
  801. // Template Cache
  802. // --------------
  803. // Manage templates stored in `<script>` blocks,
  804. // caching them for faster access.
  805. Marionette.TemplateCache = function(templateId){
  806. this.templateId = templateId;
  807. };
  808. // TemplateCache object-level methods. Manage the template
  809. // caches from these method calls instead of creating
  810. // your own TemplateCache instances
  811. _.extend(Marionette.TemplateCache, {
  812. templateCaches: {},
  813. // Get the specified template by id. Either
  814. // retrieves the cached version, or loads it
  815. // from the DOM.
  816. get: function(templateId){
  817. var that = this;
  818. var cachedTemplate = this.templateCaches[templateId];
  819. if (!cachedTemplate){
  820. cachedTemplate = new Marionette.TemplateCache(templateId);
  821. this.templateCaches[templateId] = cachedTemplate;
  822. }
  823. return cachedTemplate.load();
  824. },
  825. // Clear templates from the cache. If no arguments
  826. // are specified, clears all templates:
  827. // `clear()`
  828. //
  829. // If arguments are specified, clears each of the
  830. // specified templates from the cache:
  831. // `clear("#t1", "#t2", "...")`
  832. clear: function(){
  833. var i;
  834. var length = arguments.length;
  835. if (length > 0){
  836. for(i=0; i<length; i++){
  837. delete this.templateCaches[arguments[i]];
  838. }
  839. } else {
  840. this.templateCaches = {};
  841. }
  842. }
  843. });
  844. // TemplateCache instance methods, allowing each
  845. // template cache object to manage it's own state
  846. // and know whether or not it has been loaded
  847. _.extend(Marionette.TemplateCache.prototype, {
  848. // Internal method to load the template asynchronously.
  849. load: function(){
  850. var that = this;
  851. // Guard clause to prevent loading this template more than once
  852. if (this.compiledTemplate){
  853. return this.compiledTemplate;
  854. }
  855. // Load the template and compile it
  856. var template = this.loadTemplate(this.templateId);
  857. this.compiledTemplate = this.compileTemplate(template);
  858. return this.compiledTemplate;
  859. },
  860. // Load a template from the DOM, by default. Override
  861. // this method to provide your own template retrieval,
  862. // such as asynchronous loading from a server.
  863. loadTemplate: function(templateId){
  864. var template = $(templateId).html();
  865. if (!template || template.length === 0){
  866. var msg = "Could not find template: '" + templateId + "'";
  867. var err = new Error(msg);
  868. err.name = "NoTemplateError";
  869. throw err;
  870. }
  871. return template;
  872. },
  873. // Pre-compile the template before caching it. Override
  874. // this method if you do not need to pre-compile a template
  875. // (JST / RequireJS for example) or if you want to change
  876. // the template engine used (Handebars, etc).
  877. compileTemplate: function(rawTemplate){
  878. return _.template(rawTemplate);
  879. }
  880. });
  881. // Renderer
  882. // --------
  883. // Render a template with data by passing in the template
  884. // selector and the data to render.
  885. Marionette.Renderer = {
  886. // Render a template with data. The `template` parameter is
  887. // passed to the `TemplateCache` object to retrieve the
  888. // template function. Override this method to provide your own
  889. // custom rendering and template handling for all of Marionette.
  890. render: function(template, data){
  891. var templateFunc = Marionette.TemplateCache.get(template);
  892. var html = templateFunc(data);
  893. return html;
  894. }
  895. };
  896. // Callbacks
  897. // ---------
  898. // A simple way of managing a collection of callbacks
  899. // and executing them at a later point in time, using jQuery's
  900. // `Deferred` object.
  901. Marionette.Callbacks = function(){
  902. this.deferred = $.Deferred();
  903. this.promise = this.deferred.promise();
  904. };
  905. _.extend(Marionette.Callbacks.prototype, {
  906. // Add a callback to be executed. Callbacks added here are
  907. // guaranteed to execute, even if they are added after the
  908. // `run` method is called.
  909. add: function(callback, contextOverride){
  910. this.promise.done(function(context, options){
  911. if (contextOverride){ context = contextOverride; }
  912. callback.call(context, options);
  913. });
  914. },
  915. // Run all registered callbacks with the context specified.
  916. // Additional callbacks can be added after this has been run
  917. // and they will still be executed.
  918. run: function(options, context){
  919. this.deferred.resolve(context, options);
  920. }
  921. });
  922. // Event Aggregator
  923. // ----------------
  924. // A pub-sub object that can be used to decouple various parts
  925. // of an application through event-driven architecture.
  926. Marionette.EventAggregator = function(options){
  927. _.extend(this, options);
  928. };
  929. _.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
  930. // Assumes the event aggregator itself is the
  931. // object being bound to.
  932. bindTo: function(eventName, callback, context){
  933. return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
  934. }
  935. });
  936. // Helpers
  937. // -------
  938. // For slicing `arguments` in functions
  939. var slice = Array.prototype.slice;
  940. return Marionette;
  941. })(Backbone, _, window.jQuery || window.Zepto || window.ender);