_TemplatedMixin.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. define("dijit/_TemplatedMixin", [
  2. "dojo/_base/lang", // lang.getObject
  3. "dojo/touch",
  4. "./_WidgetBase",
  5. "dojo/string", // string.substitute string.trim
  6. "dojo/cache", // dojo.cache
  7. "dojo/_base/array", // array.forEach
  8. "dojo/_base/declare", // declare
  9. "dojo/dom-construct", // domConstruct.destroy, domConstruct.toDom
  10. "dojo/_base/sniff", // has("ie")
  11. "dojo/_base/unload", // unload.addOnWindowUnload
  12. "dojo/_base/window" // win.doc
  13. ], function(lang, touch, _WidgetBase, string, cache, array, declare, domConstruct, has, unload, win) {
  14. /*=====
  15. var _WidgetBase = dijit._WidgetBase;
  16. =====*/
  17. // module:
  18. // dijit/_TemplatedMixin
  19. // summary:
  20. // Mixin for widgets that are instantiated from a template
  21. var _TemplatedMixin = declare("dijit._TemplatedMixin", null, {
  22. // summary:
  23. // Mixin for widgets that are instantiated from a template
  24. // templateString: [protected] String
  25. // A string that represents the widget template.
  26. // Use in conjunction with dojo.cache() to load from a file.
  27. templateString: null,
  28. // templatePath: [protected deprecated] String
  29. // Path to template (HTML file) for this widget relative to dojo.baseUrl.
  30. // Deprecated: use templateString with require([... "dojo/text!..."], ...) instead
  31. templatePath: null,
  32. // skipNodeCache: [protected] Boolean
  33. // If using a cached widget template nodes poses issues for a
  34. // particular widget class, it can set this property to ensure
  35. // that its template is always re-built from a string
  36. _skipNodeCache: false,
  37. // _earlyTemplatedStartup: Boolean
  38. // A fallback to preserve the 1.0 - 1.3 behavior of children in
  39. // templates having their startup called before the parent widget
  40. // fires postCreate. Defaults to 'false', causing child widgets to
  41. // have their .startup() called immediately before a parent widget
  42. // .startup(), but always after the parent .postCreate(). Set to
  43. // 'true' to re-enable to previous, arguably broken, behavior.
  44. _earlyTemplatedStartup: false,
  45. /*=====
  46. // _attachPoints: [private] String[]
  47. // List of widget attribute names associated with data-dojo-attach-point=... in the
  48. // template, ex: ["containerNode", "labelNode"]
  49. _attachPoints: [],
  50. =====*/
  51. /*=====
  52. // _attachEvents: [private] Handle[]
  53. // List of connections associated with data-dojo-attach-event=... in the
  54. // template
  55. _attachEvents: [],
  56. =====*/
  57. constructor: function(){
  58. this._attachPoints = [];
  59. this._attachEvents = [];
  60. },
  61. _stringRepl: function(tmpl){
  62. // summary:
  63. // Does substitution of ${foo} type properties in template string
  64. // tags:
  65. // private
  66. var className = this.declaredClass, _this = this;
  67. // Cache contains a string because we need to do property replacement
  68. // do the property replacement
  69. return string.substitute(tmpl, this, function(value, key){
  70. if(key.charAt(0) == '!'){ value = lang.getObject(key.substr(1), false, _this); }
  71. if(typeof value == "undefined"){ throw new Error(className+" template:"+key); } // a debugging aide
  72. if(value == null){ return ""; }
  73. // Substitution keys beginning with ! will skip the transform step,
  74. // in case a user wishes to insert unescaped markup, e.g. ${!foo}
  75. return key.charAt(0) == "!" ? value :
  76. // Safer substitution, see heading "Attribute values" in
  77. // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
  78. value.toString().replace(/"/g,"""); //TODO: add &amp? use encodeXML method?
  79. }, this);
  80. },
  81. buildRendering: function(){
  82. // summary:
  83. // Construct the UI for this widget from a template, setting this.domNode.
  84. // tags:
  85. // protected
  86. if(!this.templateString){
  87. this.templateString = cache(this.templatePath, {sanitize: true});
  88. }
  89. // Lookup cached version of template, and download to cache if it
  90. // isn't there already. Returns either a DomNode or a string, depending on
  91. // whether or not the template contains ${foo} replacement parameters.
  92. var cached = _TemplatedMixin.getCachedTemplate(this.templateString, this._skipNodeCache);
  93. var node;
  94. if(lang.isString(cached)){
  95. node = domConstruct.toDom(this._stringRepl(cached));
  96. if(node.nodeType != 1){
  97. // Flag common problems such as templates with multiple top level nodes (nodeType == 11)
  98. throw new Error("Invalid template: " + cached);
  99. }
  100. }else{
  101. // if it's a node, all we have to do is clone it
  102. node = cached.cloneNode(true);
  103. }
  104. this.domNode = node;
  105. // Call down to _Widget.buildRendering() to get base classes assigned
  106. // TODO: change the baseClass assignment to _setBaseClassAttr
  107. this.inherited(arguments);
  108. // recurse through the node, looking for, and attaching to, our
  109. // attachment points and events, which should be defined on the template node.
  110. this._attachTemplateNodes(node, function(n,p){ return n.getAttribute(p); });
  111. this._beforeFillContent(); // hook for _WidgetsInTemplateMixin
  112. this._fillContent(this.srcNodeRef);
  113. },
  114. _beforeFillContent: function(){
  115. },
  116. _fillContent: function(/*DomNode*/ source){
  117. // summary:
  118. // Relocate source contents to templated container node.
  119. // this.containerNode must be able to receive children, or exceptions will be thrown.
  120. // tags:
  121. // protected
  122. var dest = this.containerNode;
  123. if(source && dest){
  124. while(source.hasChildNodes()){
  125. dest.appendChild(source.firstChild);
  126. }
  127. }
  128. },
  129. _attachTemplateNodes: function(rootNode, getAttrFunc){
  130. // summary:
  131. // Iterate through the template and attach functions and nodes accordingly.
  132. // Alternately, if rootNode is an array of widgets, then will process data-dojo-attach-point
  133. // etc. for those widgets.
  134. // description:
  135. // Map widget properties and functions to the handlers specified in
  136. // the dom node and it's descendants. This function iterates over all
  137. // nodes and looks for these properties:
  138. // * dojoAttachPoint/data-dojo-attach-point
  139. // * dojoAttachEvent/data-dojo-attach-event
  140. // rootNode: DomNode|Widget[]
  141. // the node to search for properties. All children will be searched.
  142. // getAttrFunc: Function
  143. // a function which will be used to obtain property for a given
  144. // DomNode/Widget
  145. // tags:
  146. // private
  147. var nodes = lang.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*"));
  148. var x = lang.isArray(rootNode) ? 0 : -1;
  149. for(; x<nodes.length; x++){
  150. var baseNode = (x == -1) ? rootNode : nodes[x];
  151. if(this.widgetsInTemplate && (getAttrFunc(baseNode, "dojoType") || getAttrFunc(baseNode, "data-dojo-type"))){
  152. continue;
  153. }
  154. // Process data-dojo-attach-point
  155. var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint") || getAttrFunc(baseNode, "data-dojo-attach-point");
  156. if(attachPoint){
  157. var point, points = attachPoint.split(/\s*,\s*/);
  158. while((point = points.shift())){
  159. if(lang.isArray(this[point])){
  160. this[point].push(baseNode);
  161. }else{
  162. this[point]=baseNode;
  163. }
  164. this._attachPoints.push(point);
  165. }
  166. }
  167. // Process data-dojo-attach-event
  168. var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent") || getAttrFunc(baseNode, "data-dojo-attach-event");
  169. if(attachEvent){
  170. // NOTE: we want to support attributes that have the form
  171. // "domEvent: nativeEvent; ..."
  172. var event, events = attachEvent.split(/\s*,\s*/);
  173. var trim = lang.trim;
  174. while((event = events.shift())){
  175. if(event){
  176. var thisFunc = null;
  177. if(event.indexOf(":") != -1){
  178. // oh, if only JS had tuple assignment
  179. var funcNameArr = event.split(":");
  180. event = trim(funcNameArr[0]);
  181. thisFunc = trim(funcNameArr[1]);
  182. }else{
  183. event = trim(event);
  184. }
  185. if(!thisFunc){
  186. thisFunc = event;
  187. }
  188. // Map "press", "move" and "release" to keys.touch, keys.move, keys.release
  189. this._attachEvents.push(this.connect(baseNode, touch[event] || event, thisFunc));
  190. }
  191. }
  192. }
  193. }
  194. },
  195. destroyRendering: function(){
  196. // Delete all attach points to prevent IE6 memory leaks.
  197. array.forEach(this._attachPoints, function(point){
  198. delete this[point];
  199. }, this);
  200. this._attachPoints = [];
  201. // And same for event handlers
  202. array.forEach(this._attachEvents, this.disconnect, this);
  203. this._attachEvents = [];
  204. this.inherited(arguments);
  205. }
  206. });
  207. // key is templateString; object is either string or DOM tree
  208. _TemplatedMixin._templateCache = {};
  209. _TemplatedMixin.getCachedTemplate = function(templateString, alwaysUseString){
  210. // summary:
  211. // Static method to get a template based on the templatePath or
  212. // templateString key
  213. // templateString: String
  214. // The template
  215. // alwaysUseString: Boolean
  216. // Don't cache the DOM tree for this template, even if it doesn't have any variables
  217. // returns: Mixed
  218. // Either string (if there are ${} variables that need to be replaced) or just
  219. // a DOM tree (if the node can be cloned directly)
  220. // is it already cached?
  221. var tmplts = _TemplatedMixin._templateCache;
  222. var key = templateString;
  223. var cached = tmplts[key];
  224. if(cached){
  225. try{
  226. // if the cached value is an innerHTML string (no ownerDocument) or a DOM tree created within the current document, then use the current cached value
  227. if(!cached.ownerDocument || cached.ownerDocument == win.doc){
  228. // string or node of the same document
  229. return cached;
  230. }
  231. }catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded
  232. domConstruct.destroy(cached);
  233. }
  234. templateString = string.trim(templateString);
  235. if(alwaysUseString || templateString.match(/\$\{([^\}]+)\}/g)){
  236. // there are variables in the template so all we can do is cache the string
  237. return (tmplts[key] = templateString); //String
  238. }else{
  239. // there are no variables in the template so we can cache the DOM tree
  240. var node = domConstruct.toDom(templateString);
  241. if(node.nodeType != 1){
  242. throw new Error("Invalid template: " + templateString);
  243. }
  244. return (tmplts[key] = node); //Node
  245. }
  246. };
  247. if(has("ie")){
  248. unload.addOnWindowUnload(function(){
  249. var cache = _TemplatedMixin._templateCache;
  250. for(var key in cache){
  251. var value = cache[key];
  252. if(typeof value == "object"){ // value is either a string or a DOM node template
  253. domConstruct.destroy(value);
  254. }
  255. delete cache[key];
  256. }
  257. });
  258. }
  259. // These arguments can be specified for widgets which are used in templates.
  260. // Since any widget can be specified as sub widgets in template, mix it
  261. // into the base widget class. (This is a hack, but it's effective.)
  262. lang.extend(_WidgetBase,{
  263. dojoAttachEvent: "",
  264. dojoAttachPoint: ""
  265. });
  266. return _TemplatedMixin;
  267. });