source: Dev/branches/rest-dojo-ui/client/dojo/html.js @ 263

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