source: Dev/trunk/src/client/dojox/html/_base.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 15.0 KB
Line 
1define([
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,"&lt;$1Style").replace(/<(\/?)link\b/ig,"&lt;$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,"&lt;$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});
Note: See TracBrowser for help on using the repository browser.