PageRenderTime 28ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

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

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