PageRenderTime 25ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/ajax/libs/backbone.marionette/0.7.1/backbone.marionette.js

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