PageRenderTime 29ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/ajax/libs/backbone.marionette/0.8.1-bundled/backbone.marionette.js

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