viewEngine.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /**
  2. * Durandal 2.1.0 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved.
  3. * Available via the MIT license.
  4. * see: http://durandaljs.com or https://github.com/BlueSpire/Durandal for details.
  5. */
  6. /**
  7. * The viewEngine module provides information to the viewLocator module which is used to locate the view's source file. The viewEngine also transforms a view id into a view instance.
  8. * @module viewEngine
  9. * @requires system
  10. * @requires jquery
  11. */
  12. define(['durandal/system', 'jquery'], function (system, $) {
  13. var parseMarkup;
  14. if ($.parseHTML) {
  15. parseMarkup = function (html) {
  16. return $.parseHTML(html);
  17. };
  18. } else {
  19. parseMarkup = function (html) {
  20. return $(html).get();
  21. };
  22. }
  23. /**
  24. * @class ViewEngineModule
  25. * @static
  26. */
  27. return {
  28. cache:{},
  29. /**
  30. * The file extension that view source files are expected to have.
  31. * @property {string} viewExtension
  32. * @default .html
  33. */
  34. viewExtension: '.html',
  35. /**
  36. * The name of the RequireJS loader plugin used by the viewLocator to obtain the view source. (Use requirejs to map the plugin's full path).
  37. * @property {string} viewPlugin
  38. * @default text
  39. */
  40. viewPlugin: 'text',
  41. /**
  42. * Parameters passed to the RequireJS loader plugin used by the viewLocator to obtain the view source.
  43. * @property {string} viewPluginParameters
  44. * @default The empty string by default.
  45. */
  46. viewPluginParameters: '',
  47. /**
  48. * Determines if the url is a url for a view, according to the view engine.
  49. * @method isViewUrl
  50. * @param {string} url The potential view url.
  51. * @return {boolean} True if the url is a view url, false otherwise.
  52. */
  53. isViewUrl: function (url) {
  54. return url.indexOf(this.viewExtension, url.length - this.viewExtension.length) !== -1;
  55. },
  56. /**
  57. * Converts a view url into a view id.
  58. * @method convertViewUrlToViewId
  59. * @param {string} url The url to convert.
  60. * @return {string} The view id.
  61. */
  62. convertViewUrlToViewId: function (url) {
  63. return url.substring(0, url.length - this.viewExtension.length);
  64. },
  65. /**
  66. * Converts a view id into a full RequireJS path.
  67. * @method convertViewIdToRequirePath
  68. * @param {string} viewId The view id to convert.
  69. * @return {string} The require path.
  70. */
  71. convertViewIdToRequirePath: function (viewId) {
  72. var plugin = this.viewPlugin ? this.viewPlugin + '!' : '';
  73. return plugin + viewId + this.viewExtension + this.viewPluginParameters;
  74. },
  75. /**
  76. * Parses the view engine recognized markup and returns DOM elements.
  77. * @method parseMarkup
  78. * @param {string} markup The markup to parse.
  79. * @return {DOMElement[]} The elements.
  80. */
  81. parseMarkup: parseMarkup,
  82. /**
  83. * Calls `parseMarkup` and then pipes the results through `ensureSingleElement`.
  84. * @method processMarkup
  85. * @param {string} markup The markup to process.
  86. * @return {DOMElement} The view.
  87. */
  88. processMarkup: function (markup) {
  89. var allElements = this.parseMarkup(markup);
  90. return this.ensureSingleElement(allElements);
  91. },
  92. /**
  93. * Converts an array of elements into a single element. White space and comments are removed. If a single element does not remain, then the elements are wrapped.
  94. * @method ensureSingleElement
  95. * @param {DOMElement[]} allElements The elements.
  96. * @return {DOMElement} A single element.
  97. */
  98. ensureSingleElement:function(allElements){
  99. if (allElements.length == 1) {
  100. return allElements[0];
  101. }
  102. var withoutCommentsOrEmptyText = [];
  103. for (var i = 0; i < allElements.length; i++) {
  104. var current = allElements[i];
  105. if (current.nodeType != 8) {
  106. if (current.nodeType == 3) {
  107. var result = /\S/.test(current.nodeValue);
  108. if (!result) {
  109. continue;
  110. }
  111. }
  112. withoutCommentsOrEmptyText.push(current);
  113. }
  114. }
  115. if (withoutCommentsOrEmptyText.length > 1) {
  116. return $(withoutCommentsOrEmptyText).wrapAll('<div class="durandal-wrapper"></div>').parent().get(0);
  117. }
  118. return withoutCommentsOrEmptyText[0];
  119. },
  120. /**
  121. * Gets the view associated with the id from the cache of parsed views.
  122. * @method tryGetViewFromCache
  123. * @param {string} id The view id to lookup in the cache.
  124. * @return {DOMElement|null} The cached view or null if it's not in the cache.
  125. */
  126. tryGetViewFromCache:function(id) {
  127. return this.cache[id];
  128. },
  129. /**
  130. * Puts the view associated with the id into the cache of parsed views.
  131. * @method putViewInCache
  132. * @param {string} id The view id whose view should be cached.
  133. * @param {DOMElement} view The view to cache.
  134. */
  135. putViewInCache: function (id, view) {
  136. this.cache[id] = view;
  137. },
  138. /**
  139. * Creates the view associated with the view id.
  140. * @method createView
  141. * @param {string} viewId The view id whose view should be created.
  142. * @return {Promise} A promise of the view.
  143. */
  144. createView: function(viewId) {
  145. var that = this;
  146. var requirePath = this.convertViewIdToRequirePath(viewId);
  147. var existing = this.tryGetViewFromCache(requirePath);
  148. if (existing) {
  149. return system.defer(function(dfd) {
  150. dfd.resolve(existing.cloneNode(true));
  151. }).promise();
  152. }
  153. return system.defer(function(dfd) {
  154. system.acquire(requirePath).then(function(markup) {
  155. var element = that.processMarkup(markup);
  156. element.setAttribute('data-view', viewId);
  157. that.putViewInCache(requirePath, element);
  158. dfd.resolve(element.cloneNode(true));
  159. }).fail(function(err) {
  160. that.createFallbackView(viewId, requirePath, err).then(function(element) {
  161. element.setAttribute('data-view', viewId);
  162. that.cache[requirePath] = element;
  163. dfd.resolve(element.cloneNode(true));
  164. });
  165. });
  166. }).promise();
  167. },
  168. /**
  169. * Called when a view cannot be found to provide the opportunity to locate or generate a fallback view. Mainly used to ease development.
  170. * @method createFallbackView
  171. * @param {string} viewId The view id whose view should be created.
  172. * @param {string} requirePath The require path that was attempted.
  173. * @param {Error} requirePath The error that was returned from the attempt to locate the default view.
  174. * @return {Promise} A promise for the fallback view.
  175. */
  176. createFallbackView: function (viewId, requirePath, err) {
  177. var that = this,
  178. message = 'View Not Found. Searched for "' + viewId + '" via path "' + requirePath + '".';
  179. return system.defer(function(dfd) {
  180. dfd.resolve(that.processMarkup('<div class="durandal-view-404">' + message + '</div>'));
  181. }).promise();
  182. }
  183. };
  184. });