You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
430 lines
15 KiB
430 lines
15 KiB
define([ |
|
"dojo/_base/declare", |
|
"dojo/Deferred", |
|
"dojo/dom-construct", |
|
"dojo/html", |
|
"dojo/_base/kernel", |
|
"dojo/_base/lang", |
|
"dojo/ready", |
|
"dojo/_base/sniff", |
|
"dojo/_base/url", |
|
"dojo/_base/xhr", |
|
"dojo/when", |
|
"dojo/_base/window" |
|
], function(declare, Deferred, domConstruct, htmlUtil, kernel, lang, ready, has, _Url, xhrUtil, when, windowUtil){ |
|
|
|
/* |
|
Status: don't know where this will all live exactly |
|
Need to pull in the implementation of the various helper methods |
|
Some can be static method, others maybe methods of the ContentSetter (?) |
|
|
|
Gut the ContentPane, replace its _setContent with our own call to dojox/html.set() |
|
|
|
|
|
*/ |
|
var html = kernel.getObject("dojox.html", true); |
|
|
|
if(has("ie")){ |
|
var alphaImageLoader = /(AlphaImageLoader\([^)]*?src=(['"]))(?![a-z]+:|\/)([^\r\n;}]+?)(\2[^)]*\)\s*[;}]?)/g; |
|
} |
|
|
|
// css at-rules must be set before any css declarations according to CSS spec |
|
// match: |
|
// @import 'http://dojotoolkit.org/dojo.css'; |
|
// @import 'you/never/thought/' print; |
|
// @import url("it/would/work") tv, screen; |
|
// @import url(/did/you/now.css); |
|
// but not: |
|
// @namespace dojo "http://dojotoolkit.org/dojo.css"; /* namespace URL should always be a absolute URI */ |
|
// @charset 'utf-8'; |
|
// @media print{ #menuRoot {display:none;} } |
|
|
|
// we adjust all paths that dont start on '/' or contains ':' |
|
//(?![a-z]+:|\/) |
|
|
|
var cssPaths = /(?:(?:@import\s*(['"])(?![a-z]+:|\/)([^\r\n;{]+?)\1)|url\(\s*(['"]?)(?![a-z]+:|\/)([^\r\n;]+?)\3\s*\))([a-z, \s]*[;}]?)/g; |
|
|
|
var adjustCssPaths = html._adjustCssPaths = function(cssUrl, cssText){ |
|
// summary: |
|
// adjusts relative paths in cssText to be relative to cssUrl |
|
// a path is considered relative if it doesn't start with '/' and not contains ':' |
|
// description: |
|
// Say we fetch a HTML page from level1/page.html |
|
// It has some inline CSS: |
|
// | @import "css/page.css" tv, screen; |
|
// | ... |
|
// | background-image: url(images/aplhaimage.png); |
|
// |
|
// as we fetched this HTML and therefore this CSS |
|
// from level1/page.html, these paths needs to be adjusted to: |
|
// | @import 'level1/css/page.css' tv, screen; |
|
// | ... |
|
// | background-image: url(level1/images/alphaimage.png); |
|
// |
|
// In IE it will also adjust relative paths in AlphaImageLoader() |
|
// | filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/alphaimage.png'); |
|
// will be adjusted to: |
|
// | filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='level1/images/alphaimage.png'); |
|
// |
|
// Please note that any relative paths in AlphaImageLoader in external css files wont work, as |
|
// the paths in AlphaImageLoader is MUST be declared relative to the HTML page, |
|
// not relative to the CSS file that declares it |
|
|
|
if(!cssText || !cssUrl){ return; } |
|
|
|
// support the ImageAlphaFilter if it exists, most people use it in IE 6 for transparent PNGs |
|
// We are NOT going to kill it in IE 7 just because the PNGs work there. Somebody might have |
|
// other uses for it. |
|
// If user want to disable css filter in IE6 he/she should |
|
// unset filter in a declaration that just IE 6 doesn't understands |
|
// like * > .myselector { filter:none; } |
|
if(alphaImageLoader){ |
|
cssText = cssText.replace(alphaImageLoader, function(ignore, pre, delim, url, post){ |
|
return pre + (new _Url(cssUrl, './'+url).toString()) + post; |
|
}); |
|
} |
|
|
|
return cssText.replace(cssPaths, function(ignore, delimStr, strUrl, delimUrl, urlUrl, media){ |
|
if(strUrl){ |
|
return '@import "' + (new _Url(cssUrl, './'+strUrl).toString()) + '"' + media; |
|
}else{ |
|
return 'url(' + (new _Url(cssUrl, './'+urlUrl).toString()) + ')' + media; |
|
} |
|
}); |
|
}; |
|
|
|
// attributepaths one tag can have multiple paths, example: |
|
// <input src="..." style="url(..)"/> or <a style="url(..)" href=".."> |
|
// <img style='filter:progid...AlphaImageLoader(src="noticeTheSrcHereRunsThroughHtmlSrc")' src="img"> |
|
var htmlAttrPaths = /(<[a-z][a-z0-9]*\s[^>]*)(?:(href|src)=(['"]?)([^>]*?)\3|style=(['"]?)([^>]*?)\5)([^>]*>)/gi; |
|
|
|
var adjustHtmlPaths = html._adjustHtmlPaths = function(htmlUrl, cont){ |
|
var url = htmlUrl || "./"; |
|
|
|
return cont.replace(htmlAttrPaths, |
|
function(tag, start, name, delim, relUrl, delim2, cssText, end){ |
|
return start + (name ? |
|
(name + '=' + delim + (new _Url(url, relUrl).toString()) + delim) |
|
: ('style=' + delim2 + adjustCssPaths(url, cssText) + delim2) |
|
) + end; |
|
} |
|
); |
|
}; |
|
|
|
var snarfStyles = html._snarfStyles = function (/*String*/cssUrl, /*String*/cont, /*Array*/styles){ |
|
/**************** cut out all <style> and <link rel="stylesheet" href=".."> **************/ |
|
// also return any attributes from this tag (might be a media attribute) |
|
// if cssUrl is set it will adjust paths accordingly |
|
styles.attributes = []; |
|
|
|
cont = cont.replace(/<[!][-][-](.|\s)*?[-][-]>/g, |
|
function(comment){ |
|
return comment.replace(/<(\/?)style\b/ig,"<$1Style").replace(/<(\/?)link\b/ig,"<$1Link").replace(/@import "/ig,"@ import \""); |
|
} |
|
); |
|
return cont.replace(/(?:<style([^>]*)>([\s\S]*?)<\/style>|<link\s+(?=[^>]*rel=['"]?stylesheet)([^>]*?href=(['"])([^>]*?)\4[^>\/]*)\/?>)/gi, |
|
function(ignore, styleAttr, cssText, linkAttr, delim, href){ |
|
// trim attribute |
|
var i, attr = (styleAttr||linkAttr||"").replace(/^\s*([\s\S]*?)\s*$/i, "$1"); |
|
if(cssText){ |
|
i = styles.push(cssUrl ? adjustCssPaths(cssUrl, cssText) : cssText); |
|
}else{ |
|
i = styles.push('@import "' + href + '";'); |
|
attr = attr.replace(/\s*(?:rel|href)=(['"])?[^\s]*\1\s*/gi, ""); // remove rel=... and href=... |
|
} |
|
if(attr){ |
|
attr = attr.split(/\s+/);// split on both "\n", "\t", " " etc |
|
var atObj = {}, tmp; |
|
for(var j = 0, e = attr.length; j < e; j++){ |
|
tmp = attr[j].split('='); // split name='value' |
|
atObj[tmp[0]] = tmp[1].replace(/^\s*['"]?([\s\S]*?)['"]?\s*$/, "$1"); // trim and remove '' |
|
} |
|
styles.attributes[i - 1] = atObj; |
|
} |
|
return ""; |
|
} |
|
); |
|
}; |
|
|
|
var snarfScripts = html._snarfScripts = function(cont, byRef){ |
|
// summary: |
|
// strips out script tags from cont |
|
// byRef: |
|
// byRef = {errBack:function(){/*add your download error code here*/, downloadRemote: true(default false)}} |
|
// byRef will have {code: 'jscode'} when this scope leaves |
|
byRef.code = ""; |
|
|
|
//Update script tags nested in comments so that the script tag collector doesn't pick |
|
//them up. |
|
cont = cont.replace(/<[!][-][-](.|\s)*?[-][-]>/g, |
|
function(comment){ |
|
return comment.replace(/<(\/?)script\b/ig,"<$1Script"); |
|
} |
|
); |
|
|
|
function download(src){ |
|
if(byRef.downloadRemote){ |
|
// console.debug('downloading',src); |
|
//Fix up src, in case there were entity character encodings in it. |
|
//Probably only need to worry about a subset. |
|
src = src.replace(/&([a-z0-9#]+);/g, function(m, name) { |
|
switch(name) { |
|
case "amp" : return "&"; |
|
case "gt" : return ">"; |
|
case "lt" : return "<"; |
|
default: |
|
return name.charAt(0)=="#" ? String.fromCharCode(name.substring(1)) : "&"+name+";"; |
|
} |
|
}); |
|
xhrUtil.get({ |
|
url: src, |
|
sync: true, |
|
load: function(code){ |
|
if(byRef.code !=="") { |
|
code = "\n" + code; |
|
} |
|
byRef.code += code+";"; |
|
}, |
|
error: byRef.errBack |
|
}); |
|
} |
|
} |
|
|
|
// match <script>, <script type="text/..., but not <script type="dojo(/method)... |
|
return cont.replace(/<script\s*(?![^>]*type=['"]?(?:dojo\/|text\/html\b))[^>]*?(?:src=(['"]?)([^>]*?)\1[^>]*)?>([\s\S]*?)<\/script>/gi, |
|
function(ignore, delim, src, code){ |
|
if(src){ |
|
download(src); |
|
}else{ |
|
if(byRef.code !=="") { |
|
code = "\n" + code; |
|
} |
|
byRef.code += code+";"; |
|
} |
|
return ""; |
|
} |
|
); |
|
}; |
|
|
|
var evalInGlobal = html.evalInGlobal = function(code, appendNode){ |
|
// we do our own eval here as dojo.eval doesn't eval in global crossbrowser |
|
// This work X browser but but it relies on a DOM |
|
// plus it doesn't return anything, thats unrelevant here but not for dojo core |
|
appendNode = appendNode || windowUtil.doc.body; |
|
var n = appendNode.ownerDocument.createElement('script'); |
|
n.type = "text/javascript"; |
|
appendNode.appendChild(n); |
|
n.text = code; // DOM 1 says this should work |
|
}; |
|
|
|
html._ContentSetter = declare(/*===== "dojox/html._ContentSetter", =====*/ htmlUtil._ContentSetter, { |
|
// adjustPaths: Boolean |
|
// Adjust relative paths in html string content to point to this page |
|
// Only useful if you grab content from a another folder than the current one |
|
adjustPaths: false, |
|
referencePath: ".", |
|
renderStyles: false, |
|
|
|
executeScripts: false, |
|
scriptHasHooks: false, |
|
scriptHookReplacement: null, |
|
|
|
_renderStyles: function(styles){ |
|
// insert css from content into document head |
|
this._styleNodes = []; |
|
var st, att, cssText, doc = this.node.ownerDocument; |
|
var head = doc.getElementsByTagName('head')[0]; |
|
|
|
for(var i = 0, e = styles.length; i < e; i++){ |
|
cssText = styles[i]; att = styles.attributes[i]; |
|
st = doc.createElement('style'); |
|
st.setAttribute("type", "text/css"); // this is required in CSS spec! |
|
|
|
for(var x in att){ |
|
st.setAttribute(x, att[x]); |
|
} |
|
|
|
this._styleNodes.push(st); |
|
head.appendChild(st); // must insert into DOM before setting cssText |
|
|
|
if(st.styleSheet){ // IE |
|
st.styleSheet.cssText = cssText; |
|
}else{ // w3c |
|
st.appendChild(doc.createTextNode(cssText)); |
|
} |
|
} |
|
}, |
|
|
|
empty: function() { |
|
this.inherited("empty", arguments); |
|
|
|
// empty out the styles array from any previous use |
|
this._styles = []; |
|
}, |
|
|
|
onBegin: function() { |
|
// summary: |
|
// Called after instantiation, but before set(); |
|
// It allows modification of any of the object properties - including the node and content |
|
// provided - before the set operation actually takes place |
|
// This implementation extends that of dojo/html/_ContentSetter |
|
// to add handling for adjustPaths, renderStyles on the html string content before it is set |
|
this.inherited("onBegin", arguments); |
|
|
|
var cont = this.content, |
|
node = this.node; |
|
|
|
var styles = this._styles;// init vars |
|
this._code = null; // do not execute scripts from previous content set |
|
|
|
if(lang.isString(cont)){ |
|
if(this.adjustPaths && this.referencePath){ |
|
cont = adjustHtmlPaths(this.referencePath, cont); |
|
} |
|
|
|
if(this.renderStyles || this.cleanContent){ |
|
cont = snarfStyles(this.referencePath, cont, styles); |
|
} |
|
|
|
// because of a bug in IE, the script tag that is first in html hierarchy doesnt make it into the DOM |
|
// when content is innerHTML'ed, so we can't use dojo/query to retrieve scripts from DOM |
|
if(this.executeScripts){ |
|
var _t = this; |
|
var byRef = { |
|
downloadRemote: true, |
|
errBack:function(e){ |
|
_t._onError.call(_t, 'Exec', 'Error downloading remote script in "'+_t.id+'"', e); |
|
} |
|
}; |
|
cont = snarfScripts(cont, byRef); |
|
this._code = byRef.code; |
|
} |
|
} |
|
this.content = cont; |
|
}, |
|
|
|
onEnd: function() { |
|
// summary: |
|
// Called after set(), when the new content has been pushed into the node |
|
// It provides an opportunity for post-processing before handing back the node to the caller |
|
// This implementation extends that of dojo/html/_ContentSetter |
|
|
|
var code = this._code, |
|
styles = this._styles; |
|
|
|
// clear old stylenodes from the DOM |
|
// these were added by the last set call |
|
// (in other words, if you dont keep and reuse the ContentSetter for a particular node |
|
// .. you'll have no practical way to do this) |
|
if(this._styleNodes && this._styleNodes.length){ |
|
while(this._styleNodes.length){ |
|
domConstruct.destroy(this._styleNodes.pop()); |
|
} |
|
} |
|
// render new style nodes |
|
if(this.renderStyles && styles && styles.length){ |
|
this._renderStyles(styles); |
|
} |
|
|
|
// Deferred to signal when this function is complete |
|
var d = new Deferred(); |
|
|
|
// Setup function to call onEnd() in the superclass, for parsing, and resolve the above Deferred when |
|
// parsing is complete. |
|
var superClassOnEndMethod = this.getInherited(arguments), |
|
args = arguments, |
|
callSuperclass = lang.hitch(this, function(){ |
|
superClassOnEndMethod.apply(this, args); |
|
|
|
// If parser ran (parseContent == true), wait for it to finish, otherwise call d.resolve() immediately |
|
when(this.parseDeferred, function(){ d.resolve(); }); |
|
}); |
|
|
|
if(this.executeScripts && code){ |
|
// Evaluate any <script> blocks in the content |
|
if(this.cleanContent){ |
|
// clean JS from html comments and other crap that browser |
|
// parser takes care of in a normal page load |
|
code = code.replace(/(<!--|(?:\/\/)?-->|<!\[CDATA\[|\]\]>)/g, ''); |
|
} |
|
if(this.scriptHasHooks){ |
|
// replace _container_ with this.scriptHookReplace() |
|
// the scriptHookReplacement can be a string |
|
// or a function, which when invoked returns the string you want to substitute in |
|
code = code.replace(/_container_(?!\s*=[^=])/g, this.scriptHookReplacement); |
|
} |
|
try{ |
|
evalInGlobal(code, this.node); |
|
}catch(e){ |
|
this._onError('Exec', 'Error eval script in '+this.id+', '+e.message, e); |
|
} |
|
|
|
// Finally, use ready() to wait for any require() calls from the <script> blocks to complete, |
|
// then call onEnd() in the superclass, for parsing, and when that is done resolve the Deferred. |
|
// For 2.0, remove the call to ready() (or this whole if() branch?) since the parser can do loading for us. |
|
ready(callSuperclass); |
|
}else{ |
|
// There were no <script>'s to execute, so immediately call onEnd() in the superclass, and |
|
// when the parser finishes running, resolve the Deferred. |
|
callSuperclass(); |
|
} |
|
|
|
// Return a promise that resolves after the ready() call completes, and after the parser finishes running. |
|
return d.promise; |
|
}, |
|
|
|
tearDown: function() { |
|
this.inherited(arguments); |
|
delete this._styles; |
|
// only tear down -or another set() - will explicitly throw away the |
|
// references to the style nodes we added |
|
if(this._styleNodes && this._styleNodes.length){ |
|
while(this._styleNodes.length){ |
|
domConstruct.destroy(this._styleNodes.pop()); |
|
} |
|
} |
|
delete this._styleNodes; |
|
// reset the defaults from the prototype |
|
// XXX: not sure if this is the correct intended behaviour, it was originally |
|
// dojo.getObject(this.declaredClass).prototype which will not work with anonymous |
|
// modules |
|
lang.mixin(this, html._ContentSetter.prototype); |
|
} |
|
|
|
}); |
|
|
|
html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){ |
|
// TODO: add all the other options |
|
// summary: |
|
// inserts (replaces) the given content into the given node |
|
// node: |
|
// the parent element that will receive the content |
|
// cont: |
|
// the content to be set on the parent element. |
|
// This can be an html string, a node reference or a NodeList, dojo/NodeList, Array or other enumerable list of nodes |
|
// params: |
|
// Optional flags/properties to configure the content-setting. See dojo/html/_ContentSetter |
|
// example: |
|
// A safe string/node/nodelist content replacement/injection with hooks for extension |
|
// Example Usage: |
|
// | require(["dojo/html", "dojox/html", "dojo/domReady!"], function(html){ |
|
// | html.set(node, "some string"); |
|
// | html.set(node, contentNode, {options}); |
|
// | html.set(node, myNode.childNodes, {options}); |
|
// | }); |
|
|
|
if(!params){ |
|
// simple and fast |
|
return htmlUtil._setNodeContent(node, cont, true); |
|
}else{ |
|
// more options but slower |
|
var op = new html._ContentSetter(lang.mixin( |
|
params, |
|
{ content: cont, node: node } |
|
)); |
|
return op.set(); |
|
} |
|
}; |
|
|
|
return html; |
|
});
|
|
|