source: Dev/trunk/src/client/dojo/html.js @ 493

Last change on this file since 493 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 13.0 KB
Line 
1define(["./_base/kernel", "./_base/lang", "./_base/array", "./_base/declare", "./dom", "./dom-construct", "./parser"],
2        function(kernel, lang, darray, declare, dom, domConstruct, parser){
3        // module:
4        //              dojo/html
5
6        // the parser might be needed..
7
8        // idCounter is incremented with each instantiation to allow assignment of a unique id for tracking, logging purposes
9        var idCounter = 0;
10
11        var html = {
12                // summary:
13                //              TODOC
14
15                _secureForInnerHtml: function(/*String*/ cont){
16                        // summary:
17                        //              removes !DOCTYPE and title elements from the html string.
18                        //
19                        //              khtml is picky about dom faults, you can't attach a style or `<title>` node as child of body
20                        //              must go into head, so we need to cut out those tags
21                        // cont:
22                        //              An html string for insertion into the dom
23                        //
24                        return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String
25                },
26
27                // Deprecated, should use dojo/dom-constuct.empty() directly, remove in 2.0.
28                _emptyNode: domConstruct.empty,
29
30                _setNodeContent: function(/*DomNode*/ node, /*String|DomNode|NodeList*/ cont){
31                        // summary:
32                        //              inserts the given content into the given node
33                        // node:
34                        //              the parent element
35                        // content:
36                        //              the content to be set on the parent element.
37                        //              This can be an html string, a node reference or a NodeList, dojo/NodeList, Array or other enumerable list of nodes
38
39                        // always empty
40                        domConstruct.empty(node);
41
42                        if(cont){
43                                if(typeof cont == "string"){
44                                        cont = domConstruct.toDom(cont, node.ownerDocument);
45                                }
46                                if(!cont.nodeType && lang.isArrayLike(cont)){
47                                        // handle as enumerable, but it may shrink as we enumerate it
48                                        for(var startlen=cont.length, i=0; i<cont.length; i=startlen==cont.length ? i+1 : 0){
49                                                domConstruct.place( cont[i], node, "last");
50                                        }
51                                }else{
52                                        // pass nodes, documentFragments and unknowns through to dojo.place
53                                        domConstruct.place(cont, node, "last");
54                                }
55                        }
56
57                        // return DomNode
58                        return node;
59                },
60
61                // we wrap up the content-setting operation in a object
62                _ContentSetter: declare("dojo.html._ContentSetter", null, {
63                        // node: DomNode|String
64                        //              An node which will be the parent element that we set content into
65                        node: "",
66
67                        // content: String|DomNode|DomNode[]
68                        //              The content to be placed in the node. Can be an HTML string, a node reference, or a enumerable list of nodes
69                        content: "",
70
71                        // id: String?
72                        //              Usually only used internally, and auto-generated with each instance
73                        id: "",
74
75                        // cleanContent: Boolean
76                        //              Should the content be treated as a full html document,
77                        //              and the real content stripped of <html>, <body> wrapper before injection
78                        cleanContent: false,
79
80                        // extractContent: Boolean
81                        //              Should the content be treated as a full html document,
82                        //              and the real content stripped of `<html> <body>` wrapper before injection
83                        extractContent: false,
84
85                        // parseContent: Boolean
86                        //              Should the node by passed to the parser after the new content is set
87                        parseContent: false,
88
89                        // parserScope: String
90                        //              Flag passed to parser.  Root for attribute names to search for.   If scopeName is dojo,
91                        //              will search for data-dojo-type (or dojoType).  For backwards compatibility
92                        //              reasons defaults to dojo._scopeName (which is "dojo" except when
93                        //              multi-version support is used, when it will be something like dojo16, dojo20, etc.)
94                        parserScope: kernel._scopeName,
95
96                        // startup: Boolean
97                        //              Start the child widgets after parsing them.       Only obeyed if parseContent is true.
98                        startup: true,
99
100                        // lifecycle methods
101                        constructor: function(/*Object*/ params, /*String|DomNode*/ node){
102                                // summary:
103                                //              Provides a configurable, extensible object to wrap the setting on content on a node
104                                //              call the set() method to actually set the content..
105
106                                // the original params are mixed directly into the instance "this"
107                                lang.mixin(this, params || {});
108
109                                // give precedence to params.node vs. the node argument
110                                // and ensure its a node, not an id string
111                                node = this.node = dom.byId( this.node || node );
112
113                                if(!this.id){
114                                        this.id = [
115                                                "Setter",
116                                                (node) ? node.id || node.tagName : "",
117                                                idCounter++
118                                        ].join("_");
119                                }
120                        },
121                        set: function(/* String|DomNode|NodeList? */ cont, /*Object?*/ params){
122                                // summary:
123                                //              front-end to the set-content sequence
124                                // cont:
125                                //              An html string, node or enumerable list of nodes for insertion into the dom
126                                //              If not provided, the object's content property will be used
127                                if(undefined !== cont){
128                                        this.content = cont;
129                                }
130                                // in the re-use scenario, set needs to be able to mixin new configuration
131                                if(params){
132                                        this._mixin(params);
133                                }
134
135                                this.onBegin();
136                                this.setContent();
137
138                                var ret = this.onEnd();
139
140                                if(ret && ret.then){
141                                        // Make dojox/html/_ContentSetter.set() return a Promise that resolves when load and parse complete.
142                                        return ret;
143                                }else{
144                                        // Vanilla dojo/html._ContentSetter.set() returns a DOMNode for back compat.   For 2.0, switch it to
145                                        // return a Deferred like above.
146                                        return this.node;
147                                }
148                        },
149
150                        setContent: function(){
151                                // summary:
152                                //              sets the content on the node
153
154                                var node = this.node;
155                                if(!node){
156                                        // can't proceed
157                                        throw new Error(this.declaredClass + ": setContent given no node");
158                                }
159                                try{
160                                        node = html._setNodeContent(node, this.content);
161                                }catch(e){
162                                        // check if a domfault occurs when we are appending this.errorMessage
163                                        // like for instance if domNode is a UL and we try append a DIV
164
165                                        // FIXME: need to allow the user to provide a content error message string
166                                        var errMess = this.onContentError(e);
167                                        try{
168                                                node.innerHTML = errMess;
169                                        }catch(e){
170                                                console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e);
171                                        }
172                                }
173                                // always put back the node for the next method
174                                this.node = node; // DomNode
175                        },
176
177                        empty: function(){
178                                // summary:
179                                //              cleanly empty out existing content
180
181                                // If there is a parse in progress, cancel it.
182                                if(this.parseDeferred){
183                                        if(!this.parseDeferred.isResolved()){
184                                                this.parseDeferred.cancel();
185                                        }
186                                        delete this.parseDeferred;
187                                }
188
189                                // destroy any widgets from a previous run
190                                // NOTE: if you don't want this you'll need to empty
191                                // the parseResults array property yourself to avoid bad things happening
192                                if(this.parseResults && this.parseResults.length){
193                                        darray.forEach(this.parseResults, function(w){
194                                                if(w.destroy){
195                                                        w.destroy();
196                                                }
197                                        });
198                                        delete this.parseResults;
199                                }
200                                // this is fast, but if you know its already empty or safe, you could
201                                // override empty to skip this step
202                                domConstruct.empty(this.node);
203                        },
204
205                        onBegin: function(){
206                                // summary:
207                                //              Called after instantiation, but before set();
208                                //              It allows modification of any of the object properties -
209                                //              including the node and content provided - before the set operation actually takes place
210                                //              This default implementation checks for cleanContent and extractContent flags to
211                                //              optionally pre-process html string content
212                                var cont = this.content;
213
214                                if(lang.isString(cont)){
215                                        if(this.cleanContent){
216                                                cont = html._secureForInnerHtml(cont);
217                                        }
218
219                                        if(this.extractContent){
220                                                var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
221                                                if(match){ cont = match[1]; }
222                                        }
223                                }
224
225                                // clean out the node and any cruft associated with it - like widgets
226                                this.empty();
227
228                                this.content = cont;
229                                return this.node; // DomNode
230                        },
231
232                        onEnd: function(){
233                                // summary:
234                                //              Called after set(), when the new content has been pushed into the node
235                                //              It provides an opportunity for post-processing before handing back the node to the caller
236                                //              This default implementation checks a parseContent flag to optionally run the dojo parser over the new content
237                                if(this.parseContent){
238                                        // populates this.parseResults and this.parseDeferred if you need those..
239                                        this._parse();
240                                }
241                                return this.node; // DomNode
242                                // TODO: for 2.0 return a Promise indicating that the parse completed.
243                        },
244
245                        tearDown: function(){
246                                // summary:
247                                //              manually reset the Setter instance if its being re-used for example for another set()
248                                // description:
249                                //              tearDown() is not called automatically.
250                                //              In normal use, the Setter instance properties are simply allowed to fall out of scope
251                                //              but the tearDown method can be called to explicitly reset this instance.
252                                delete this.parseResults;
253                                delete this.parseDeferred;
254                                delete this.node;
255                                delete this.content;
256                        },
257
258                        onContentError: function(err){
259                                return "Error occurred setting content: " + err;
260                        },
261
262                        onExecError: function(err){
263                                return "Error occurred executing scripts: " + err;
264                        },
265
266                        _mixin: function(params){
267                                // mix properties/methods into the instance
268                                // TODO: the intention with tearDown is to put the Setter's state
269                                // back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params)
270                                // so we could do something here to move the original properties aside for later restoration
271                                var empty = {}, key;
272                                for(key in params){
273                                        if(key in empty){ continue; }
274                                        // TODO: here's our opportunity to mask the properties we don't consider configurable/overridable
275                                        // .. but history shows we'll almost always guess wrong
276                                        this[key] = params[key];
277                                }
278                        },
279                        _parse: function(){
280                                // summary:
281                                //              runs the dojo parser over the node contents, storing any results in this.parseResults
282                                //              and the parse promise in this.parseDeferred
283                                //              Any errors resulting from parsing are passed to _onError for handling
284
285                                var rootNode = this.node;
286                                try{
287                                        // store the results (widgets, whatever) for potential retrieval
288                                        var inherited = {};
289                                        darray.forEach(["dir", "lang", "textDir"], function(name){
290                                                if(this[name]){
291                                                        inherited[name] = this[name];
292                                                }
293                                        }, this);
294                                        var self = this;
295                                        this.parseDeferred = parser.parse({
296                                                rootNode: rootNode,
297                                                noStart: !this.startup,
298                                                inherited: inherited,
299                                                scope: this.parserScope
300                                        }).then(function(results){
301                                                        return self.parseResults = results;
302                                                }, function(e){
303                                                        self._onError('Content', e, "Error parsing in _ContentSetter#" + this.id);
304                                                });
305                                }catch(e){
306                                        this._onError('Content', e, "Error parsing in _ContentSetter#" + this.id);
307                                }
308                        },
309
310                        _onError: function(type, err, consoleText){
311                                // summary:
312                                //              shows user the string that is returned by on[type]Error
313                                //              override/implement on[type]Error and return your own string to customize
314                                var errText = this['on' + type + 'Error'].call(this, err);
315                                if(consoleText){
316                                        console.error(consoleText, err);
317                                }else if(errText){ // a empty string won't change current content
318                                        html._setNodeContent(this.node, errText, true);
319                                }
320                        }
321                }), // end declare()
322
323                set: function(/*DomNode*/ node, /*String|DomNode|NodeList*/ cont, /*Object?*/ params){
324                        // summary:
325                        //              inserts (replaces) the given content into the given node. dojo/dom-construct.place(cont, node, "only")
326                        //              may be a better choice for simple HTML insertion.
327                        // description:
328                        //              Unless you need to use the params capabilities of this method, you should use
329                        //              dojo/dom-construct.place(cont, node, "only"). dojo/dom-construct..place() has more robust support for injecting
330                        //              an HTML string into the DOM, but it only handles inserting an HTML string as DOM
331                        //              elements, or inserting a DOM node. dojo/dom-construct..place does not handle NodeList insertions
332                        //              dojo/dom-construct.place(cont, node, "only"). dojo/dom-construct.place() has more robust support for injecting
333                        //              an HTML string into the DOM, but it only handles inserting an HTML string as DOM
334                        //              elements, or inserting a DOM node. dojo/dom-construct.place does not handle NodeList insertions
335                        //              or the other capabilities as defined by the params object for this method.
336                        // node:
337                        //              the parent element that will receive the content
338                        // cont:
339                        //              the content to be set on the parent element.
340                        //              This can be an html string, a node reference or a NodeList, dojo/NodeList, Array or other enumerable list of nodes
341                        // params:
342                        //              Optional flags/properties to configure the content-setting. See dojo/html/_ContentSetter
343                        // example:
344                        //              A safe string/node/nodelist content replacement/injection with hooks for extension
345                        //              Example Usage:
346                        //      |       html.set(node, "some string");
347                        //      |       html.set(node, contentNode, {options});
348                        //      |       html.set(node, myNode.childNodes, {options});
349                        if(undefined == cont){
350                                console.warn("dojo.html.set: no cont argument provided, using empty string");
351                                cont = "";
352                        }
353                        if(!params){
354                                // simple and fast
355                                return html._setNodeContent(node, cont, true);
356                        }else{
357                                // more options but slower
358                                // note the arguments are reversed in order, to match the convention for instantiation via the parser
359                                var op = new html._ContentSetter(lang.mixin(
360                                        params,
361                                        { content: cont, node: node }
362                                ));
363                                return op.set();
364                        }
365                }
366        };
367        lang.setObject("dojo.html", html);
368
369        return html;
370});
Note: See TracBrowser for help on using the repository browser.