PageRenderTime 24ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

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

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