PageRenderTime 82ms CodeModel.GetById 36ms RepoModel.GetById 1ms app.codeStats 0ms

/ajax/libs/backbone.marionette/0.8.4/amd/backbone.marionette.js

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