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