]> git.wh0rd.org - tt-rss.git/blame - lib/dijit/_Templated.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dijit / _Templated.js
CommitLineData
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
8if(!dojo._hasResource["dijit._Templated"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9dojo._hasResource["dijit._Templated"] = true;
2f01fe57
AD
10dojo.provide("dijit._Templated");
11dojo.require("dijit._Widget");
12dojo.require("dojo.string");
13dojo.require("dojo.parser");
14dojo.require("dojo.cache");
81bea17a
AD
15
16
17dojo.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 &amp? 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
279dijit._Templated._templateCache = {};
280
281dijit._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 328if(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.)
344dojo.extend(dijit._Widget,{
345 dojoAttachEvent: "",
346 dojoAttachPoint: "",
347 waiRole: "",
348 waiState:""
2f01fe57 349});
81bea17a 350
2f01fe57 351}