]> git.wh0rd.org - tt-rss.git/blob - lib/dijit/_TemplatedMixin.js.uncompressed.js
upgrade dojo to 1.8.3 (refs #570)
[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/sniff", // has("ie")
11 "dojo/_base/unload" // unload.addOnWindowUnload
12 ], function(lang, touch, _WidgetBase, string, cache, array, declare, domConstruct, has, unload) {
13
14 // module:
15 // dijit/_TemplatedMixin
16
17 var _TemplatedMixin = declare("dijit._TemplatedMixin", null, {
18 // summary:
19 // Mixin for widgets that are instantiated from a template
20
21 // templateString: [protected] String
22 // A string that represents the widget template.
23 // Use in conjunction with dojo.cache() to load from a file.
24 templateString: null,
25
26 // templatePath: [protected deprecated] String
27 // Path to template (HTML file) for this widget relative to dojo.baseUrl.
28 // Deprecated: use templateString with require([... "dojo/text!..."], ...) instead
29 templatePath: null,
30
31 // skipNodeCache: [protected] Boolean
32 // If using a cached widget template nodes poses issues for a
33 // particular widget class, it can set this property to ensure
34 // that its template is always re-built from a string
35 _skipNodeCache: false,
36
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 /*=====
47 // _attachPoints: [private] String[]
48 // List of widget attribute names associated with data-dojo-attach-point=... in the
49 // template, ex: ["containerNode", "labelNode"]
50 _attachPoints: [],
51
52 // _attachEvents: [private] Handle[]
53 // List of connections associated with data-dojo-attach-event=... in the
54 // template
55 _attachEvents: [],
56 =====*/
57
58 constructor: function(/*===== params, srcNodeRef =====*/){
59 // summary:
60 // Create the widget.
61 // params: Object|null
62 // Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
63 // and functions, typically callbacks like onClick.
64 // The hash can contain any of the widget's properties, excluding read-only properties.
65 // srcNodeRef: DOMNode|String?
66 // If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree.
67
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, this.ownerDocument);
108
109 var node;
110 if(lang.isString(cached)){
111 node = domConstruct.toDom(this._stringRepl(cached), this.ownerDocument);
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 //
163 // - dojoAttachPoint/data-dojo-attach-point
164 // - dojoAttachEvent/data-dojo-attach-event
165 // rootNode: DomNode|Widget[]
166 // the node to search for properties. All children will be searched.
167 // getAttrFunc: Function
168 // a function which will be used to obtain property for a given
169 // DomNode/Widget
170 // tags:
171 // private
172
173 var nodes = lang.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*"));
174 var x = lang.isArray(rootNode) ? 0 : -1;
175 for(; x < 0 || nodes[x]; x++){ // don't access nodes.length on IE, see #14346
176 var baseNode = (x == -1) ? rootNode : nodes[x];
177 if(this.widgetsInTemplate && (getAttrFunc(baseNode, "dojoType") || getAttrFunc(baseNode, "data-dojo-type"))){
178 continue;
179 }
180 // Process data-dojo-attach-point
181 var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint") || getAttrFunc(baseNode, "data-dojo-attach-point");
182 if(attachPoint){
183 var point, points = attachPoint.split(/\s*,\s*/);
184 while((point = points.shift())){
185 if(lang.isArray(this[point])){
186 this[point].push(baseNode);
187 }else{
188 this[point]=baseNode;
189 }
190 this._attachPoints.push(point);
191 }
192 }
193
194 // Process data-dojo-attach-event
195 var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent") || getAttrFunc(baseNode, "data-dojo-attach-event");
196 if(attachEvent){
197 // NOTE: we want to support attributes that have the form
198 // "domEvent: nativeEvent; ..."
199 var event, events = attachEvent.split(/\s*,\s*/);
200 var trim = lang.trim;
201 while((event = events.shift())){
202 if(event){
203 var thisFunc = null;
204 if(event.indexOf(":") != -1){
205 // oh, if only JS had tuple assignment
206 var funcNameArr = event.split(":");
207 event = trim(funcNameArr[0]);
208 thisFunc = trim(funcNameArr[1]);
209 }else{
210 event = trim(event);
211 }
212 if(!thisFunc){
213 thisFunc = event;
214 }
215 // Map "press", "move" and "release" to keys.touch, keys.move, keys.release
216 this._attachEvents.push(this.connect(baseNode, touch[event] || event, thisFunc));
217 }
218 }
219 }
220 }
221 },
222
223 destroyRendering: function(){
224 // Delete all attach points to prevent IE6 memory leaks.
225 array.forEach(this._attachPoints, function(point){
226 delete this[point];
227 }, this);
228 this._attachPoints = [];
229
230 // And same for event handlers
231 array.forEach(this._attachEvents, this.disconnect, this);
232 this._attachEvents = [];
233
234 this.inherited(arguments);
235 }
236 });
237
238 // key is templateString; object is either string or DOM tree
239 _TemplatedMixin._templateCache = {};
240
241 _TemplatedMixin.getCachedTemplate = function(templateString, alwaysUseString, doc){
242 // summary:
243 // Static method to get a template based on the templatePath or
244 // templateString key
245 // templateString: String
246 // The template
247 // alwaysUseString: Boolean
248 // Don't cache the DOM tree for this template, even if it doesn't have any variables
249 // doc: Document?
250 // The target document. Defaults to document global if unspecified.
251 // returns: Mixed
252 // Either string (if there are ${} variables that need to be replaced) or just
253 // a DOM tree (if the node can be cloned directly)
254
255 // is it already cached?
256 var tmplts = _TemplatedMixin._templateCache;
257 var key = templateString;
258 var cached = tmplts[key];
259 if(cached){
260 try{
261 // if the cached value is an innerHTML string (no ownerDocument) or a DOM tree created within the
262 // current document, then use the current cached value
263 if(!cached.ownerDocument || cached.ownerDocument == (doc || document)){
264 // string or node of the same document
265 return cached;
266 }
267 }catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded
268 domConstruct.destroy(cached);
269 }
270
271 templateString = string.trim(templateString);
272
273 if(alwaysUseString || templateString.match(/\$\{([^\}]+)\}/g)){
274 // there are variables in the template so all we can do is cache the string
275 return (tmplts[key] = templateString); //String
276 }else{
277 // there are no variables in the template so we can cache the DOM tree
278 var node = domConstruct.toDom(templateString, doc);
279 if(node.nodeType != 1){
280 throw new Error("Invalid template: " + templateString);
281 }
282 return (tmplts[key] = node); //Node
283 }
284 };
285
286 if(has("ie")){
287 unload.addOnWindowUnload(function(){
288 var cache = _TemplatedMixin._templateCache;
289 for(var key in cache){
290 var value = cache[key];
291 if(typeof value == "object"){ // value is either a string or a DOM node template
292 domConstruct.destroy(value);
293 }
294 delete cache[key];
295 }
296 });
297 }
298
299 // These arguments can be specified for widgets which are used in templates.
300 // Since any widget can be specified as sub widgets in template, mix it
301 // into the base widget class. (This is a hack, but it's effective.).
302 // Remove for 2.0. Also, hide from API doc parser.
303 lang.extend(_WidgetBase, /*===== {} || =====*/ {
304 dojoAttachEvent: "",
305 dojoAttachPoint: ""
306 });
307
308 return _TemplatedMixin;
309 });