1 | define(["./_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 | }); |
---|