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