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