source: Dev/branches/rest-dojo-ui/client/dojox/html/_base.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 13.5 KB
Line 
1define([
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,"&lt;$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});
Note: See TracBrowser for help on using the repository browser.