source: Dev/branches/rest-dojo-ui/client/dijit/_TemplatedMixin.js @ 274

Last change on this file since 274 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 10.5 KB
Line 
1define([
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});
Note: See TracBrowser for help on using the repository browser.