[483] | 1 | define([ |
---|
| 2 | "dojo/_base/declare", |
---|
| 3 | "dojo/Deferred", |
---|
| 4 | "dojo/dom-construct", |
---|
| 5 | "dojo/html", |
---|
| 6 | "dojo/_base/kernel", |
---|
| 7 | "dojo/_base/lang", |
---|
| 8 | "dojo/ready", |
---|
| 9 | "dojo/_base/sniff", |
---|
| 10 | "dojo/_base/url", |
---|
| 11 | "dojo/_base/xhr", |
---|
| 12 | "dojo/when", |
---|
| 13 | "dojo/_base/window" |
---|
| 14 | ], function(declare, Deferred, domConstruct, htmlUtil, kernel, lang, ready, has, _Url, xhrUtil, when, windowUtil){ |
---|
| 15 | |
---|
| 16 | /* |
---|
| 17 | Status: don't know where this will all live exactly |
---|
| 18 | Need to pull in the implementation of the various helper methods |
---|
| 19 | Some can be static method, others maybe methods of the ContentSetter (?) |
---|
| 20 | |
---|
| 21 | Gut the ContentPane, replace its _setContent with our own call to dojox.html.set() |
---|
| 22 | |
---|
| 23 | |
---|
| 24 | */ |
---|
| 25 | var html = kernel.getObject("dojox.html", true); |
---|
| 26 | |
---|
| 27 | if(has("ie")){ |
---|
| 28 | var alphaImageLoader = /(AlphaImageLoader\([^)]*?src=(['"]))(?![a-z]+:|\/)([^\r\n;}]+?)(\2[^)]*\)\s*[;}]?)/g; |
---|
| 29 | } |
---|
| 30 | |
---|
| 31 | // css at-rules must be set before any css declarations according to CSS spec |
---|
| 32 | // match: |
---|
| 33 | // @import 'http://dojotoolkit.org/dojo.css'; |
---|
| 34 | // @import 'you/never/thought/' print; |
---|
| 35 | // @import url("it/would/work") tv, screen; |
---|
| 36 | // @import url(/did/you/now.css); |
---|
| 37 | // but not: |
---|
| 38 | // @namespace dojo "http://dojotoolkit.org/dojo.css"; /* namespace URL should always be a absolute URI */ |
---|
| 39 | // @charset 'utf-8'; |
---|
| 40 | // @media print{ #menuRoot {display:none;} } |
---|
| 41 | |
---|
| 42 | // we adjust all paths that dont start on '/' or contains ':' |
---|
| 43 | //(?![a-z]+:|\/) |
---|
| 44 | |
---|
| 45 | var cssPaths = /(?:(?:@import\s*(['"])(?![a-z]+:|\/)([^\r\n;{]+?)\1)|url\(\s*(['"]?)(?![a-z]+:|\/)([^\r\n;]+?)\3\s*\))([a-z, \s]*[;}]?)/g; |
---|
| 46 | |
---|
| 47 | var adjustCssPaths = html._adjustCssPaths = function(cssUrl, cssText){ |
---|
| 48 | // summary: |
---|
| 49 | // adjusts relative paths in cssText to be relative to cssUrl |
---|
| 50 | // a path is considered relative if it doesn't start with '/' and not contains ':' |
---|
| 51 | // description: |
---|
| 52 | // Say we fetch a HTML page from level1/page.html |
---|
| 53 | // It has some inline CSS: |
---|
| 54 | // | @import "css/page.css" tv, screen; |
---|
| 55 | // | ... |
---|
| 56 | // | background-image: url(images/aplhaimage.png); |
---|
| 57 | // |
---|
| 58 | // as we fetched this HTML and therefore this CSS |
---|
| 59 | // from level1/page.html, these paths needs to be adjusted to: |
---|
| 60 | // | @import 'level1/css/page.css' tv, screen; |
---|
| 61 | // | ... |
---|
| 62 | // | background-image: url(level1/images/alphaimage.png); |
---|
| 63 | // |
---|
| 64 | // In IE it will also adjust relative paths in AlphaImageLoader() |
---|
| 65 | // | filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/alphaimage.png'); |
---|
| 66 | // will be adjusted to: |
---|
| 67 | // | filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='level1/images/alphaimage.png'); |
---|
| 68 | // |
---|
| 69 | // Please note that any relative paths in AlphaImageLoader in external css files wont work, as |
---|
| 70 | // the paths in AlphaImageLoader is MUST be declared relative to the HTML page, |
---|
| 71 | // not relative to the CSS file that declares it |
---|
| 72 | |
---|
| 73 | if(!cssText || !cssUrl){ return; } |
---|
| 74 | |
---|
| 75 | // support the ImageAlphaFilter if it exists, most people use it in IE 6 for transparent PNGs |
---|
| 76 | // We are NOT going to kill it in IE 7 just because the PNGs work there. Somebody might have |
---|
| 77 | // other uses for it. |
---|
| 78 | // If user want to disable css filter in IE6 he/she should |
---|
| 79 | // unset filter in a declaration that just IE 6 doesn't understands |
---|
| 80 | // like * > .myselector { filter:none; } |
---|
| 81 | if(alphaImageLoader){ |
---|
| 82 | cssText = cssText.replace(alphaImageLoader, function(ignore, pre, delim, url, post){ |
---|
| 83 | return pre + (new _Url(cssUrl, './'+url).toString()) + post; |
---|
| 84 | }); |
---|
| 85 | } |
---|
| 86 | |
---|
| 87 | return cssText.replace(cssPaths, function(ignore, delimStr, strUrl, delimUrl, urlUrl, media){ |
---|
| 88 | if(strUrl){ |
---|
| 89 | return '@import "' + (new _Url(cssUrl, './'+strUrl).toString()) + '"' + media; |
---|
| 90 | }else{ |
---|
| 91 | return 'url(' + (new _Url(cssUrl, './'+urlUrl).toString()) + ')' + media; |
---|
| 92 | } |
---|
| 93 | }); |
---|
| 94 | }; |
---|
| 95 | |
---|
| 96 | // attributepaths one tag can have multiple paths, example: |
---|
| 97 | // <input src="..." style="url(..)"/> or <a style="url(..)" href=".."> |
---|
| 98 | // <img style='filter:progid...AlphaImageLoader(src="noticeTheSrcHereRunsThroughHtmlSrc")' src="img"> |
---|
| 99 | var htmlAttrPaths = /(<[a-z][a-z0-9]*\s[^>]*)(?:(href|src)=(['"]?)([^>]*?)\3|style=(['"]?)([^>]*?)\5)([^>]*>)/gi; |
---|
| 100 | |
---|
| 101 | var adjustHtmlPaths = html._adjustHtmlPaths = function(htmlUrl, cont){ |
---|
| 102 | var url = htmlUrl || "./"; |
---|
| 103 | |
---|
| 104 | return cont.replace(htmlAttrPaths, |
---|
| 105 | function(tag, start, name, delim, relUrl, delim2, cssText, end){ |
---|
| 106 | return start + (name ? |
---|
| 107 | (name + '=' + delim + (new _Url(url, relUrl).toString()) + delim) |
---|
| 108 | : ('style=' + delim2 + adjustCssPaths(url, cssText) + delim2) |
---|
| 109 | ) + end; |
---|
| 110 | } |
---|
| 111 | ); |
---|
| 112 | }; |
---|
| 113 | |
---|
| 114 | var snarfStyles = html._snarfStyles = function (/*String*/cssUrl, /*String*/cont, /*Array*/styles){ |
---|
| 115 | /**************** cut out all <style> and <link rel="stylesheet" href=".."> **************/ |
---|
| 116 | // also return any attributes from this tag (might be a media attribute) |
---|
| 117 | // if cssUrl is set it will adjust paths accordingly |
---|
| 118 | styles.attributes = []; |
---|
| 119 | |
---|
| 120 | cont = cont.replace(/<[!][-][-](.|\s)*?[-][-]>/g, |
---|
| 121 | function(comment){ |
---|
| 122 | return comment.replace(/<(\/?)style\b/ig,"<$1Style").replace(/<(\/?)link\b/ig,"<$1Link").replace(/@import "/ig,"@ import \""); |
---|
| 123 | } |
---|
| 124 | ); |
---|
| 125 | return cont.replace(/(?:<style([^>]*)>([\s\S]*?)<\/style>|<link\s+(?=[^>]*rel=['"]?stylesheet)([^>]*?href=(['"])([^>]*?)\4[^>\/]*)\/?>)/gi, |
---|
| 126 | function(ignore, styleAttr, cssText, linkAttr, delim, href){ |
---|
| 127 | // trim attribute |
---|
| 128 | var i, attr = (styleAttr||linkAttr||"").replace(/^\s*([\s\S]*?)\s*$/i, "$1"); |
---|
| 129 | if(cssText){ |
---|
| 130 | i = styles.push(cssUrl ? adjustCssPaths(cssUrl, cssText) : cssText); |
---|
| 131 | }else{ |
---|
| 132 | i = styles.push('@import "' + href + '";'); |
---|
| 133 | attr = attr.replace(/\s*(?:rel|href)=(['"])?[^\s]*\1\s*/gi, ""); // remove rel=... and href=... |
---|
| 134 | } |
---|
| 135 | if(attr){ |
---|
| 136 | attr = attr.split(/\s+/);// split on both "\n", "\t", " " etc |
---|
| 137 | var atObj = {}, tmp; |
---|
| 138 | for(var j = 0, e = attr.length; j < e; j++){ |
---|
| 139 | tmp = attr[j].split('='); // split name='value' |
---|
| 140 | atObj[tmp[0]] = tmp[1].replace(/^\s*['"]?([\s\S]*?)['"]?\s*$/, "$1"); // trim and remove '' |
---|
| 141 | } |
---|
| 142 | styles.attributes[i - 1] = atObj; |
---|
| 143 | } |
---|
| 144 | return ""; |
---|
| 145 | } |
---|
| 146 | ); |
---|
| 147 | }; |
---|
| 148 | |
---|
| 149 | var snarfScripts = html._snarfScripts = function(cont, byRef){ |
---|
| 150 | // summary: |
---|
| 151 | // strips out script tags from cont |
---|
| 152 | // byRef: |
---|
| 153 | // byRef = {errBack:function(){/*add your download error code here*/, downloadRemote: true(default false)}} |
---|
| 154 | // byRef will have {code: 'jscode'} when this scope leaves |
---|
| 155 | byRef.code = ""; |
---|
| 156 | |
---|
| 157 | //Update script tags nested in comments so that the script tag collector doesn't pick |
---|
| 158 | //them up. |
---|
| 159 | cont = cont.replace(/<[!][-][-](.|\s)*?[-][-]>/g, |
---|
| 160 | function(comment){ |
---|
| 161 | return comment.replace(/<(\/?)script\b/ig,"<$1Script"); |
---|
| 162 | } |
---|
| 163 | ); |
---|
| 164 | |
---|
| 165 | function download(src){ |
---|
| 166 | if(byRef.downloadRemote){ |
---|
| 167 | // console.debug('downloading',src); |
---|
| 168 | //Fix up src, in case there were entity character encodings in it. |
---|
| 169 | //Probably only need to worry about a subset. |
---|
| 170 | src = src.replace(/&([a-z0-9#]+);/g, function(m, name) { |
---|
| 171 | switch(name) { |
---|
| 172 | case "amp" : return "&"; |
---|
| 173 | case "gt" : return ">"; |
---|
| 174 | case "lt" : return "<"; |
---|
| 175 | default: |
---|
| 176 | return name.charAt(0)=="#" ? String.fromCharCode(name.substring(1)) : "&"+name+";"; |
---|
| 177 | } |
---|
| 178 | }); |
---|
| 179 | xhrUtil.get({ |
---|
| 180 | url: src, |
---|
| 181 | sync: true, |
---|
| 182 | load: function(code){ |
---|
| 183 | byRef.code += code+";"; |
---|
| 184 | }, |
---|
| 185 | error: byRef.errBack |
---|
| 186 | }); |
---|
| 187 | } |
---|
| 188 | } |
---|
| 189 | |
---|
| 190 | // match <script>, <script type="text/..., but not <script type="dojo(/method)... |
---|
| 191 | return cont.replace(/<script\s*(?![^>]*type=['"]?(?:dojo\/|text\/html\b))[^>]*?(?:src=(['"]?)([^>]*?)\1[^>]*)?>([\s\S]*?)<\/script>/gi, |
---|
| 192 | function(ignore, delim, src, code){ |
---|
| 193 | if(src){ |
---|
| 194 | download(src); |
---|
| 195 | }else{ |
---|
| 196 | byRef.code += code; |
---|
| 197 | } |
---|
| 198 | return ""; |
---|
| 199 | } |
---|
| 200 | ); |
---|
| 201 | }; |
---|
| 202 | |
---|
| 203 | var evalInGlobal = html.evalInGlobal = function(code, appendNode){ |
---|
| 204 | // we do our own eval here as dojo.eval doesn't eval in global crossbrowser |
---|
| 205 | // This work X browser but but it relies on a DOM |
---|
| 206 | // plus it doesn't return anything, thats unrelevant here but not for dojo core |
---|
| 207 | appendNode = appendNode || windowUtil.doc.body; |
---|
| 208 | var n = appendNode.ownerDocument.createElement('script'); |
---|
| 209 | n.type = "text/javascript"; |
---|
| 210 | appendNode.appendChild(n); |
---|
| 211 | n.text = code; // DOM 1 says this should work |
---|
| 212 | }; |
---|
| 213 | |
---|
| 214 | html._ContentSetter = declare(/*===== "dojox.html._ContentSetter", =====*/ htmlUtil._ContentSetter, { |
---|
| 215 | // adjustPaths: Boolean |
---|
| 216 | // Adjust relative paths in html string content to point to this page |
---|
| 217 | // Only useful if you grab content from a another folder than the current one |
---|
| 218 | adjustPaths: false, |
---|
| 219 | referencePath: ".", |
---|
| 220 | renderStyles: false, |
---|
| 221 | |
---|
| 222 | executeScripts: false, |
---|
| 223 | scriptHasHooks: false, |
---|
| 224 | scriptHookReplacement: null, |
---|
| 225 | |
---|
| 226 | _renderStyles: function(styles){ |
---|
| 227 | // insert css from content into document head |
---|
| 228 | this._styleNodes = []; |
---|
| 229 | var st, att, cssText, doc = this.node.ownerDocument; |
---|
| 230 | var head = doc.getElementsByTagName('head')[0]; |
---|
| 231 | |
---|
| 232 | for(var i = 0, e = styles.length; i < e; i++){ |
---|
| 233 | cssText = styles[i]; att = styles.attributes[i]; |
---|
| 234 | st = doc.createElement('style'); |
---|
| 235 | st.setAttribute("type", "text/css"); // this is required in CSS spec! |
---|
| 236 | |
---|
| 237 | for(var x in att){ |
---|
| 238 | st.setAttribute(x, att[x]); |
---|
| 239 | } |
---|
| 240 | |
---|
| 241 | this._styleNodes.push(st); |
---|
| 242 | head.appendChild(st); // must insert into DOM before setting cssText |
---|
| 243 | |
---|
| 244 | if(st.styleSheet){ // IE |
---|
| 245 | st.styleSheet.cssText = cssText; |
---|
| 246 | }else{ // w3c |
---|
| 247 | st.appendChild(doc.createTextNode(cssText)); |
---|
| 248 | } |
---|
| 249 | } |
---|
| 250 | }, |
---|
| 251 | |
---|
| 252 | empty: function() { |
---|
| 253 | this.inherited("empty", arguments); |
---|
| 254 | |
---|
| 255 | // empty out the styles array from any previous use |
---|
| 256 | this._styles = []; |
---|
| 257 | }, |
---|
| 258 | |
---|
| 259 | onBegin: function() { |
---|
| 260 | // summary: |
---|
| 261 | // Called after instantiation, but before set(); |
---|
| 262 | // It allows modification of any of the object properties - including the node and content |
---|
| 263 | // provided - before the set operation actually takes place |
---|
| 264 | // This implementation extends that of dojo.html._ContentSetter |
---|
| 265 | // to add handling for adjustPaths, renderStyles on the html string content before it is set |
---|
| 266 | this.inherited("onBegin", arguments); |
---|
| 267 | |
---|
| 268 | var cont = this.content, |
---|
| 269 | node = this.node; |
---|
| 270 | |
---|
| 271 | var styles = this._styles;// init vars |
---|
| 272 | |
---|
| 273 | if(lang.isString(cont)){ |
---|
| 274 | if(this.adjustPaths && this.referencePath){ |
---|
| 275 | cont = adjustHtmlPaths(this.referencePath, cont); |
---|
| 276 | } |
---|
| 277 | |
---|
| 278 | if(this.renderStyles || this.cleanContent){ |
---|
| 279 | cont = snarfStyles(this.referencePath, cont, styles); |
---|
| 280 | } |
---|
| 281 | |
---|
| 282 | // because of a bug in IE, script tags that is first in html hierarchy doesnt make it into the DOM |
---|
| 283 | // when content is innerHTML'ed, so we can't use dojo.query to retrieve scripts from DOM |
---|
| 284 | if(this.executeScripts){ |
---|
| 285 | var _t = this; |
---|
| 286 | var byRef = { |
---|
| 287 | downloadRemote: true, |
---|
| 288 | errBack:function(e){ |
---|
| 289 | _t._onError.call(_t, 'Exec', 'Error downloading remote script in "'+_t.id+'"', e); |
---|
| 290 | } |
---|
| 291 | }; |
---|
| 292 | cont = snarfScripts(cont, byRef); |
---|
| 293 | this._code = byRef.code; |
---|
| 294 | } |
---|
| 295 | } |
---|
| 296 | this.content = cont; |
---|
| 297 | }, |
---|
| 298 | |
---|
| 299 | onEnd: function() { |
---|
| 300 | // summary: |
---|
| 301 | // Called after set(), when the new content has been pushed into the node |
---|
| 302 | // It provides an opportunity for post-processing before handing back the node to the caller |
---|
| 303 | // This implementation extends that of dojo.html._ContentSetter |
---|
| 304 | |
---|
| 305 | var code = this._code, |
---|
| 306 | styles = this._styles; |
---|
| 307 | |
---|
| 308 | // clear old stylenodes from the DOM |
---|
| 309 | // these were added by the last set call |
---|
| 310 | // (in other words, if you dont keep and reuse the ContentSetter for a particular node |
---|
| 311 | // .. you'll have no practical way to do this) |
---|
| 312 | if(this._styleNodes && this._styleNodes.length){ |
---|
| 313 | while(this._styleNodes.length){ |
---|
| 314 | domConstruct.destroy(this._styleNodes.pop()); |
---|
| 315 | } |
---|
| 316 | } |
---|
| 317 | // render new style nodes |
---|
| 318 | if(this.renderStyles && styles && styles.length){ |
---|
| 319 | this._renderStyles(styles); |
---|
| 320 | } |
---|
| 321 | |
---|
| 322 | // Deferred to signal when this function is complete |
---|
| 323 | var d = new Deferred(); |
---|
| 324 | |
---|
| 325 | // Setup function to call onEnd() in the superclass, for parsing, and resolve the above Deferred when |
---|
| 326 | // parsing is complete. |
---|
| 327 | var superClassOnEndMethod = this.getInherited(arguments), |
---|
| 328 | args = arguments, |
---|
| 329 | callSuperclass = lang.hitch(this, function(){ |
---|
| 330 | superClassOnEndMethod.apply(this, args); |
---|
| 331 | |
---|
| 332 | // If parser ran (parseContent == true), wait for it to finish, otherwise call d.resolve() immediately |
---|
| 333 | when(this.parseDeferred, function(){ d.resolve(); }); |
---|
| 334 | }); |
---|
| 335 | |
---|
| 336 | if(this.executeScripts && code){ |
---|
| 337 | // Evaluate any <script> blocks in the content |
---|
| 338 | if(this.cleanContent){ |
---|
| 339 | // clean JS from html comments and other crap that browser |
---|
| 340 | // parser takes care of in a normal page load |
---|
| 341 | code = code.replace(/(<!--|(?:\/\/)?-->|<!\[CDATA\[|\]\]>)/g, ''); |
---|
| 342 | } |
---|
| 343 | if(this.scriptHasHooks){ |
---|
| 344 | // replace _container_ with this.scriptHookReplace() |
---|
| 345 | // the scriptHookReplacement can be a string |
---|
| 346 | // or a function, which when invoked returns the string you want to substitute in |
---|
| 347 | code = code.replace(/_container_(?!\s*=[^=])/g, this.scriptHookReplacement); |
---|
| 348 | } |
---|
| 349 | try{ |
---|
| 350 | evalInGlobal(code, this.node); |
---|
| 351 | }catch(e){ |
---|
| 352 | this._onError('Exec', 'Error eval script in '+this.id+', '+e.message, e); |
---|
| 353 | } |
---|
| 354 | |
---|
| 355 | // Finally, use ready() to wait for any require() calls from the <script> blocks to complete, |
---|
| 356 | // then call onEnd() in the superclass, for parsing, and when that is done resolve the Deferred. |
---|
| 357 | // For 2.0, remove the call to ready() (or this whole if() branch?) since the parser can do loading for us. |
---|
| 358 | ready(callSuperclass); |
---|
| 359 | }else{ |
---|
| 360 | // There were no <script>'s to execute, so immediately call onEnd() in the superclass, and |
---|
| 361 | // when the parser finishes running, resolve the Deferred. |
---|
| 362 | callSuperclass(); |
---|
| 363 | } |
---|
| 364 | |
---|
| 365 | // Return a promise that resolves after the ready() call completes, and after the parser finishes running. |
---|
| 366 | return d.promise; |
---|
| 367 | }, |
---|
| 368 | |
---|
| 369 | tearDown: function() { |
---|
| 370 | this.inherited(arguments); |
---|
| 371 | delete this._styles; |
---|
| 372 | // only tear down -or another set() - will explicitly throw away the |
---|
| 373 | // references to the style nodes we added |
---|
| 374 | if(this._styleNodes && this._styleNodes.length){ |
---|
| 375 | while(this._styleNodes.length){ |
---|
| 376 | domConstruct.destroy(this._styleNodes.pop()); |
---|
| 377 | } |
---|
| 378 | } |
---|
| 379 | delete this._styleNodes; |
---|
| 380 | // reset the defaults from the prototype |
---|
| 381 | // XXX: not sure if this is the correct intended behaviour, it was originally |
---|
| 382 | // dojo.getObject(this.declaredClass).prototype which will not work with anonymous |
---|
| 383 | // modules |
---|
| 384 | lang.mixin(this, html._ContentSetter.prototype); |
---|
| 385 | } |
---|
| 386 | |
---|
| 387 | }); |
---|
| 388 | |
---|
| 389 | html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){ |
---|
| 390 | // TODO: add all the other options |
---|
| 391 | // summary: |
---|
| 392 | // inserts (replaces) the given content into the given node |
---|
| 393 | // node: |
---|
| 394 | // the parent element that will receive the content |
---|
| 395 | // cont: |
---|
| 396 | // the content to be set on the parent element. |
---|
| 397 | // This can be an html string, a node reference or a NodeList, dojo/NodeList, Array or other enumerable list of nodes |
---|
| 398 | // params: |
---|
| 399 | // Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter |
---|
| 400 | // example: |
---|
| 401 | // A safe string/node/nodelist content replacement/injection with hooks for extension |
---|
| 402 | // Example Usage: |
---|
| 403 | // | dojo.html.set(node, "some string"); |
---|
| 404 | // | dojo.html.set(node, contentNode, {options}); |
---|
| 405 | // | dojo.html.set(node, myNode.childNodes, {options}); |
---|
| 406 | |
---|
| 407 | if(!params){ |
---|
| 408 | // simple and fast |
---|
| 409 | return htmlUtil._setNodeContent(node, cont, true); |
---|
| 410 | }else{ |
---|
| 411 | // more options but slower |
---|
| 412 | var op = new html._ContentSetter(lang.mixin( |
---|
| 413 | params, |
---|
| 414 | { content: cont, node: node } |
---|
| 415 | )); |
---|
| 416 | return op.set(); |
---|
| 417 | } |
---|
| 418 | }; |
---|
| 419 | |
---|
| 420 | return html; |
---|
| 421 | }); |
---|