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