_Templated.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dijit._Templated"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit._Templated"] = true;
  8. dojo.provide("dijit._Templated");
  9. dojo.require("dijit._Widget");
  10. dojo.require("dojo.string");
  11. dojo.require("dojo.parser");
  12. dojo.require("dojo.cache");
  13. dojo.declare("dijit._Templated",
  14. null,
  15. {
  16. // summary:
  17. // Mixin for widgets that are instantiated from a template
  18. // templateString: [protected] String
  19. // A string that represents the widget template. Pre-empts the
  20. // templatePath. In builds that have their strings "interned", the
  21. // templatePath is converted to an inline templateString, thereby
  22. // preventing a synchronous network call.
  23. //
  24. // Use in conjunction with dojo.cache() to load from a file.
  25. templateString: null,
  26. // templatePath: [protected deprecated] String
  27. // Path to template (HTML file) for this widget relative to dojo.baseUrl.
  28. // Deprecated: use templateString with dojo.cache() instead.
  29. templatePath: null,
  30. // widgetsInTemplate: [protected] Boolean
  31. // Should we parse the template to find widgets that might be
  32. // declared in markup inside it? False by default.
  33. widgetsInTemplate: false,
  34. // skipNodeCache: [protected] Boolean
  35. // If using a cached widget template node poses issues for a
  36. // particular widget class, it can set this property to ensure
  37. // that its template is always re-built from a string
  38. _skipNodeCache: false,
  39. // _earlyTemplatedStartup: Boolean
  40. // A fallback to preserve the 1.0 - 1.3 behavior of children in
  41. // templates having their startup called before the parent widget
  42. // fires postCreate. Defaults to 'false', causing child widgets to
  43. // have their .startup() called immediately before a parent widget
  44. // .startup(), but always after the parent .postCreate(). Set to
  45. // 'true' to re-enable to previous, arguably broken, behavior.
  46. _earlyTemplatedStartup: false,
  47. /*=====
  48. // _attachPoints: [private] String[]
  49. // List of widget attribute names associated with dojoAttachPoint=... in the
  50. // template, ex: ["containerNode", "labelNode"]
  51. _attachPoints: [],
  52. =====*/
  53. /*=====
  54. // _attachEvents: [private] Handle[]
  55. // List of connections associated with dojoAttachEvent=... in the
  56. // template
  57. _attachEvents: [],
  58. =====*/
  59. constructor: function(){
  60. this._attachPoints = [];
  61. this._attachEvents = [];
  62. },
  63. _stringRepl: function(tmpl){
  64. // summary:
  65. // Does substitution of ${foo} type properties in template string
  66. // tags:
  67. // private
  68. var className = this.declaredClass, _this = this;
  69. // Cache contains a string because we need to do property replacement
  70. // do the property replacement
  71. return dojo.string.substitute(tmpl, this, function(value, key){
  72. if(key.charAt(0) == '!'){ value = dojo.getObject(key.substr(1), false, _this); }
  73. if(typeof value == "undefined"){ throw new Error(className+" template:"+key); } // a debugging aide
  74. if(value == null){ return ""; }
  75. // Substitution keys beginning with ! will skip the transform step,
  76. // in case a user wishes to insert unescaped markup, e.g. ${!foo}
  77. return key.charAt(0) == "!" ? value :
  78. // Safer substitution, see heading "Attribute values" in
  79. // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
  80. value.toString().replace(/"/g,"""); //TODO: add &amp? use encodeXML method?
  81. }, this);
  82. },
  83. buildRendering: function(){
  84. // summary:
  85. // Construct the UI for this widget from a template, setting this.domNode.
  86. // tags:
  87. // protected
  88. // Lookup cached version of template, and download to cache if it
  89. // isn't there already. Returns either a DomNode or a string, depending on
  90. // whether or not the template contains ${foo} replacement parameters.
  91. var cached = dijit._Templated.getCachedTemplate(this.templatePath, this.templateString, this._skipNodeCache);
  92. var node;
  93. if(dojo.isString(cached)){
  94. node = dojo._toDom(this._stringRepl(cached));
  95. if(node.nodeType != 1){
  96. // Flag common problems such as templates with multiple top level nodes (nodeType == 11)
  97. throw new Error("Invalid template: " + cached);
  98. }
  99. }else{
  100. // if it's a node, all we have to do is clone it
  101. node = cached.cloneNode(true);
  102. }
  103. this.domNode = node;
  104. // Call down to _Widget.buildRendering() to get base classes assigned
  105. // TODO: change the baseClass assignment to attributeMap
  106. this.inherited(arguments);
  107. // recurse through the node, looking for, and attaching to, our
  108. // attachment points and events, which should be defined on the template node.
  109. this._attachTemplateNodes(node);
  110. if(this.widgetsInTemplate){
  111. // Store widgets that we need to start at a later point in time
  112. var cw = (this._startupWidgets = dojo.parser.parse(node, {
  113. noStart: !this._earlyTemplatedStartup,
  114. template: true,
  115. inherited: {dir: this.dir, lang: this.lang},
  116. propsThis: this, // so data-dojo-props of widgets in the template can reference "this" to refer to me
  117. scope: "dojo" // even in multi-version mode templates use dojoType/data-dojo-type
  118. }));
  119. this._supportingWidgets = dijit.findWidgets(node);
  120. this._attachTemplateNodes(cw, function(n,p){
  121. return n[p];
  122. });
  123. }
  124. this._fillContent(this.srcNodeRef);
  125. },
  126. _fillContent: function(/*DomNode*/ source){
  127. // summary:
  128. // Relocate source contents to templated container node.
  129. // this.containerNode must be able to receive children, or exceptions will be thrown.
  130. // tags:
  131. // protected
  132. var dest = this.containerNode;
  133. if(source && dest){
  134. while(source.hasChildNodes()){
  135. dest.appendChild(source.firstChild);
  136. }
  137. }
  138. },
  139. _attachTemplateNodes: function(rootNode, getAttrFunc){
  140. // summary:
  141. // Iterate through the template and attach functions and nodes accordingly.
  142. // Alternately, if rootNode is an array of widgets, then will process dojoAttachPoint
  143. // etc. for those widgets.
  144. // description:
  145. // Map widget properties and functions to the handlers specified in
  146. // the dom node and it's descendants. This function iterates over all
  147. // nodes and looks for these properties:
  148. // * dojoAttachPoint
  149. // * dojoAttachEvent
  150. // * waiRole
  151. // * waiState
  152. // rootNode: DomNode|Array[Widgets]
  153. // the node to search for properties. All children will be searched.
  154. // getAttrFunc: Function?
  155. // a function which will be used to obtain property for a given
  156. // DomNode/Widget
  157. // tags:
  158. // private
  159. getAttrFunc = getAttrFunc || function(n,p){ return n.getAttribute(p); };
  160. var nodes = dojo.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*"));
  161. var x = dojo.isArray(rootNode) ? 0 : -1;
  162. for(; x<nodes.length; x++){
  163. var baseNode = (x == -1) ? rootNode : nodes[x];
  164. if(this.widgetsInTemplate && (getAttrFunc(baseNode, "dojoType") || getAttrFunc(baseNode, "data-dojo-type"))){
  165. continue;
  166. }
  167. // Process dojoAttachPoint
  168. var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint") || getAttrFunc(baseNode, "data-dojo-attach-point");
  169. if(attachPoint){
  170. var point, points = attachPoint.split(/\s*,\s*/);
  171. while((point = points.shift())){
  172. if(dojo.isArray(this[point])){
  173. this[point].push(baseNode);
  174. }else{
  175. this[point]=baseNode;
  176. }
  177. this._attachPoints.push(point);
  178. }
  179. }
  180. // Process dojoAttachEvent
  181. var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent") || getAttrFunc(baseNode, "data-dojo-attach-event");;
  182. if(attachEvent){
  183. // NOTE: we want to support attributes that have the form
  184. // "domEvent: nativeEvent; ..."
  185. var event, events = attachEvent.split(/\s*,\s*/);
  186. var trim = dojo.trim;
  187. while((event = events.shift())){
  188. if(event){
  189. var thisFunc = null;
  190. if(event.indexOf(":") != -1){
  191. // oh, if only JS had tuple assignment
  192. var funcNameArr = event.split(":");
  193. event = trim(funcNameArr[0]);
  194. thisFunc = trim(funcNameArr[1]);
  195. }else{
  196. event = trim(event);
  197. }
  198. if(!thisFunc){
  199. thisFunc = event;
  200. }
  201. this._attachEvents.push(this.connect(baseNode, event, thisFunc));
  202. }
  203. }
  204. }
  205. // waiRole, waiState
  206. // TODO: remove this in 2.0, templates are now using role=... and aria-XXX=... attributes directicly
  207. var role = getAttrFunc(baseNode, "waiRole");
  208. if(role){
  209. dijit.setWaiRole(baseNode, role);
  210. }
  211. var values = getAttrFunc(baseNode, "waiState");
  212. if(values){
  213. dojo.forEach(values.split(/\s*,\s*/), function(stateValue){
  214. if(stateValue.indexOf('-') != -1){
  215. var pair = stateValue.split('-');
  216. dijit.setWaiState(baseNode, pair[0], pair[1]);
  217. }
  218. });
  219. }
  220. }
  221. },
  222. startup: function(){
  223. dojo.forEach(this._startupWidgets, function(w){
  224. if(w && !w._started && w.startup){
  225. w.startup();
  226. }
  227. });
  228. this.inherited(arguments);
  229. },
  230. destroyRendering: function(){
  231. // Delete all attach points to prevent IE6 memory leaks.
  232. dojo.forEach(this._attachPoints, function(point){
  233. delete this[point];
  234. }, this);
  235. this._attachPoints = [];
  236. // And same for event handlers
  237. dojo.forEach(this._attachEvents, this.disconnect, this);
  238. this._attachEvents = [];
  239. this.inherited(arguments);
  240. }
  241. }
  242. );
  243. // key is either templatePath or templateString; object is either string or DOM tree
  244. dijit._Templated._templateCache = {};
  245. dijit._Templated.getCachedTemplate = function(templatePath, templateString, alwaysUseString){
  246. // summary:
  247. // Static method to get a template based on the templatePath or
  248. // templateString key
  249. // templatePath: String||dojo.uri.Uri
  250. // The URL to get the template from.
  251. // templateString: String?
  252. // a string to use in lieu of fetching the template from a URL. Takes precedence
  253. // over templatePath
  254. // returns: Mixed
  255. // Either string (if there are ${} variables that need to be replaced) or just
  256. // a DOM tree (if the node can be cloned directly)
  257. // is it already cached?
  258. var tmplts = dijit._Templated._templateCache;
  259. var key = templateString || templatePath;
  260. var cached = tmplts[key];
  261. if(cached){
  262. try{
  263. // 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
  264. if(!cached.ownerDocument || cached.ownerDocument == dojo.doc){
  265. // string or node of the same document
  266. return cached;
  267. }
  268. }catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded
  269. dojo.destroy(cached);
  270. }
  271. // If necessary, load template string from template path
  272. if(!templateString){
  273. templateString = dojo.cache(templatePath, {sanitize: true});
  274. }
  275. templateString = dojo.string.trim(templateString);
  276. if(alwaysUseString || templateString.match(/\$\{([^\}]+)\}/g)){
  277. // there are variables in the template so all we can do is cache the string
  278. return (tmplts[key] = templateString); //String
  279. }else{
  280. // there are no variables in the template so we can cache the DOM tree
  281. var node = dojo._toDom(templateString);
  282. if(node.nodeType != 1){
  283. throw new Error("Invalid template: " + templateString);
  284. }
  285. return (tmplts[key] = node); //Node
  286. }
  287. };
  288. if(dojo.isIE){
  289. dojo.addOnWindowUnload(function(){
  290. var cache = dijit._Templated._templateCache;
  291. for(var key in cache){
  292. var value = cache[key];
  293. if(typeof value == "object"){ // value is either a string or a DOM node template
  294. dojo.destroy(value);
  295. }
  296. delete cache[key];
  297. }
  298. });
  299. }
  300. // These arguments can be specified for widgets which are used in templates.
  301. // Since any widget can be specified as sub widgets in template, mix it
  302. // into the base widget class. (This is a hack, but it's effective.)
  303. dojo.extend(dijit._Widget,{
  304. dojoAttachEvent: "",
  305. dojoAttachPoint: "",
  306. waiRole: "",
  307. waiState:""
  308. });
  309. }