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