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 | }); |
---|