diff options
author | Andrew Dolgov <[email protected]> | 2011-03-04 19:02:28 +0300 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2011-03-04 19:02:59 +0300 |
commit | a089699c8915636ba4f158d77dba9b012bc93208 (patch) | |
tree | b2d7d051f1f55d44a6be07d3ee137e5a7ccfcefb /lib/dojo/html.js | |
parent | cfad9259a6feacfa8194b1312770ae6db1ecce50 (diff) |
build custom layer of Dojo to speed up loading of tt-rss (refs #293)
Diffstat (limited to 'lib/dojo/html.js')
-rw-r--r-- | lib/dojo/html.js | 456 |
1 files changed, 321 insertions, 135 deletions
diff --git a/lib/dojo/html.js b/lib/dojo/html.js index 7c15e5812..ec5c4986e 100644 --- a/lib/dojo/html.js +++ b/lib/dojo/html.js @@ -5,141 +5,327 @@ */ -if(!dojo._hasResource["dojo.html"]){ -dojo._hasResource["dojo.html"]=true; +if(!dojo._hasResource["dojo.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.html"] = true; dojo.provide("dojo.html"); -dojo.require("dojo.parser"); -(function(){ -var _1=0,d=dojo; -dojo.html._secureForInnerHtml=function(_2){ -return _2.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig,""); -}; -dojo.html._emptyNode=dojo.empty; -dojo.html._setNodeContent=function(_3,_4){ -d.empty(_3); -if(_4){ -if(typeof _4=="string"){ -_4=d._toDom(_4,_3.ownerDocument); -} -if(!_4.nodeType&&d.isArrayLike(_4)){ -for(var _5=_4.length,i=0;i<_4.length;i=_5==_4.length?i+1:0){ -d.place(_4[i],_3,"last"); -} -}else{ -d.place(_4,_3,"last"); -} -} -return _3; -}; -dojo.declare("dojo.html._ContentSetter",null,{node:"",content:"",id:"",cleanContent:false,extractContent:false,parseContent:false,constructor:function(_6,_7){ -dojo.mixin(this,_6||{}); -_7=this.node=dojo.byId(this.node||_7); -if(!this.id){ -this.id=["Setter",(_7)?_7.id||_7.tagName:"",_1++].join("_"); -} -},set:function(_8,_9){ -if(undefined!==_8){ -this.content=_8; -} -if(_9){ -this._mixin(_9); -} -this.onBegin(); -this.setContent(); -this.onEnd(); -return this.node; -},setContent:function(){ -var _a=this.node; -if(!_a){ -throw new Error(this.declaredClass+": setContent given no node"); -} -try{ -_a=dojo.html._setNodeContent(_a,this.content); -} -catch(e){ -var _b=this.onContentError(e); -try{ -_a.innerHTML=_b; -} -catch(e){ -console.error("Fatal "+this.declaredClass+".setContent could not change content due to "+e.message,e); -} -} -this.node=_a; -},empty:function(){ -if(this.parseResults&&this.parseResults.length){ -dojo.forEach(this.parseResults,function(w){ -if(w.destroy){ -w.destroy(); -} -}); -delete this.parseResults; -} -dojo.html._emptyNode(this.node); -},onBegin:function(){ -var _c=this.content; -if(dojo.isString(_c)){ -if(this.cleanContent){ -_c=dojo.html._secureForInnerHtml(_c); -} -if(this.extractContent){ -var _d=_c.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im); -if(_d){ -_c=_d[1]; -} -} -} -this.empty(); -this.content=_c; -return this.node; -},onEnd:function(){ -if(this.parseContent){ -this._parse(); -} -return this.node; -},tearDown:function(){ -delete this.parseResults; -delete this.node; -delete this.content; -},onContentError:function(_e){ -return "Error occured setting content: "+_e; -},_mixin:function(_f){ -var _10={},key; -for(key in _f){ -if(key in _10){ -continue; -} -this[key]=_f[key]; -} -},_parse:function(){ -var _11=this.node; -try{ -this.parseResults=dojo.parser.parse({rootNode:_11,dir:this.dir,lang:this.lang}); -} -catch(e){ -this._onError("Content",e,"Error parsing in _ContentSetter#"+this.id); -} -},_onError:function(_12,err,_13){ -var _14=this["on"+_12+"Error"].call(this,err); -if(_13){ -console.error(_13,err); -}else{ -if(_14){ -dojo.html._setNodeContent(this.node,_14,true); -} -} -}}); -dojo.html.set=function(_15,_16,_17){ -if(undefined==_16){ -console.warn("dojo.html.set: no cont argument provided, using empty string"); -_16=""; -} -if(!_17){ -return dojo.html._setNodeContent(_15,_16,true); -}else{ -var op=new dojo.html._ContentSetter(dojo.mixin(_17,{content:_16,node:_15})); -return op.set(); -} -}; + +// the parser might be needed.. +dojo.require("dojo.parser"); + +(function(){ // private scope, sort of a namespace + + // idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes + var idCounter = 0, + d = dojo; + + dojo.html._secureForInnerHtml = function(/*String*/ cont){ + // summary: + // removes !DOCTYPE and title elements from the html string. + // + // khtml is picky about dom faults, you can't attach a style or <title> node as child of body + // must go into head, so we need to cut out those tags + // cont: + // An html string for insertion into the dom + // + return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String + }; + +/*==== + dojo.html._emptyNode = function(node){ + // summary: + // removes all child nodes from the given node + // node: DOMNode + // the parent element + }; +=====*/ + dojo.html._emptyNode = dojo.empty; + + dojo.html._setNodeContent = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont){ + // summary: + // inserts the given content into the given node + // node: + // the parent element + // content: + // 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 + + // always empty + d.empty(node); + + if(cont) { + if(typeof cont == "string") { + cont = d._toDom(cont, node.ownerDocument); + } + if(!cont.nodeType && d.isArrayLike(cont)) { + // handle as enumerable, but it may shrink as we enumerate it + for(var startlen=cont.length, i=0; i<cont.length; i=startlen==cont.length ? i+1 : 0) { + d.place( cont[i], node, "last"); + } + } else { + // pass nodes, documentFragments and unknowns through to dojo.place + d.place(cont, node, "last"); + } + } + + // return DomNode + return node; + }; + + // we wrap up the content-setting operation in a object + dojo.declare("dojo.html._ContentSetter", null, + { + // node: DomNode|String + // An node which will be the parent element that we set content into + node: "", + + // content: String|DomNode|DomNode[] + // The content to be placed in the node. Can be an HTML string, a node reference, or a enumerable list of nodes + content: "", + + // id: String? + // Usually only used internally, and auto-generated with each instance + id: "", + + // cleanContent: Boolean + // Should the content be treated as a full html document, + // and the real content stripped of <html>, <body> wrapper before injection + cleanContent: false, + + // extractContent: Boolean + // Should the content be treated as a full html document, and the real content stripped of <html>, <body> wrapper before injection + extractContent: false, + + // parseContent: Boolean + // Should the node by passed to the parser after the new content is set + parseContent: false, + + // lifecyle methods + constructor: function(/* Object */params, /* String|DomNode */node){ + // summary: + // Provides a configurable, extensible object to wrap the setting on content on a node + // call the set() method to actually set the content.. + + // the original params are mixed directly into the instance "this" + dojo.mixin(this, params || {}); + + // give precedence to params.node vs. the node argument + // and ensure its a node, not an id string + node = this.node = dojo.byId( this.node || node ); + + if(!this.id){ + this.id = [ + "Setter", + (node) ? node.id || node.tagName : "", + idCounter++ + ].join("_"); + } + }, + set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){ + // summary: + // front-end to the set-content sequence + // cont: + // An html string, node or enumerable list of nodes for insertion into the dom + // If not provided, the object's content property will be used + if(undefined !== cont){ + this.content = cont; + } + // in the re-use scenario, set needs to be able to mixin new configuration + if(params){ + this._mixin(params); + } + + this.onBegin(); + this.setContent(); + this.onEnd(); + + return this.node; + }, + setContent: function(){ + // summary: + // sets the content on the node + + var node = this.node; + if(!node) { + // can't proceed + throw new Error(this.declaredClass + ": setContent given no node"); + } + try{ + node = dojo.html._setNodeContent(node, this.content); + }catch(e){ + // check if a domfault occurs when we are appending this.errorMessage + // like for instance if domNode is a UL and we try append a DIV + + // FIXME: need to allow the user to provide a content error message string + var errMess = this.onContentError(e); + try{ + node.innerHTML = errMess; + }catch(e){ + console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e); + } + } + // always put back the node for the next method + this.node = node; // DomNode + }, + + empty: function() { + // summary + // cleanly empty out existing content + + // destroy any widgets from a previous run + // NOTE: if you dont want this you'll need to empty + // the parseResults array property yourself to avoid bad things happenning + if(this.parseResults && this.parseResults.length) { + dojo.forEach(this.parseResults, function(w) { + if(w.destroy){ + w.destroy(); + } + }); + delete this.parseResults; + } + // this is fast, but if you know its already empty or safe, you could + // override empty to skip this step + dojo.html._emptyNode(this.node); + }, + + 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 default implementation checks for cleanContent and extractContent flags to + // optionally pre-process html string content + var cont = this.content; + + if(dojo.isString(cont)){ + if(this.cleanContent){ + cont = dojo.html._secureForInnerHtml(cont); + } + + if(this.extractContent){ + var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im); + if(match){ cont = match[1]; } + } + } + + // clean out the node and any cruft associated with it - like widgets + this.empty(); + + this.content = cont; + return this.node; /* DomNode */ + }, + + 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 default implementation checks a parseContent flag to optionally run the dojo parser over the new content + if(this.parseContent){ + // populates this.parseResults if you need those.. + this._parse(); + } + return this.node; /* DomNode */ + }, + + tearDown: function(){ + // summary + // manually reset the Setter instance if its being re-used for example for another set() + // description + // tearDown() is not called automatically. + // In normal use, the Setter instance properties are simply allowed to fall out of scope + // but the tearDown method can be called to explicitly reset this instance. + delete this.parseResults; + delete this.node; + delete this.content; + }, + + onContentError: function(err){ + return "Error occured setting content: " + err; + }, + + _mixin: function(params){ + // mix properties/methods into the instance + // TODO: the intention with tearDown is to put the Setter's state + // back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params) + // so we could do something here to move the original properties aside for later restoration + var empty = {}, key; + for(key in params){ + if(key in empty){ continue; } + // TODO: here's our opportunity to mask the properties we dont consider configurable/overridable + // .. but history shows we'll almost always guess wrong + this[key] = params[key]; + } + }, + _parse: function(){ + // summary: + // runs the dojo parser over the node contents, storing any results in this.parseResults + // Any errors resulting from parsing are passed to _onError for handling + + var rootNode = this.node; + try{ + // store the results (widgets, whatever) for potential retrieval + this.parseResults = dojo.parser.parse({ + rootNode: rootNode, + dir: this.dir, + lang: this.lang + }); + }catch(e){ + this._onError('Content', e, "Error parsing in _ContentSetter#"+this.id); + } + }, + + _onError: function(type, err, consoleText){ + // summary: + // shows user the string that is returned by on[type]Error + // overide/implement on[type]Error and return your own string to customize + var errText = this['on' + type + 'Error'].call(this, err); + if(consoleText){ + console.error(consoleText, err); + }else if(errText){ // a empty string won't change current content + dojo.html._setNodeContent(this.node, errText, true); + } + } + }); // end dojo.declare() + + dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){ + // summary: + // inserts (replaces) the given content into the given node. dojo.place(cont, node, "only") + // may be a better choice for simple HTML insertion. + // description: + // Unless you need to use the params capabilities of this method, you should use + // dojo.place(cont, node, "only"). dojo.place() has more robust support for injecting + // an HTML string into the DOM, but it only handles inserting an HTML string as DOM + // elements, or inserting a DOM node. dojo.place does not handle NodeList insertions + // or the other capabilities as defined by the params object for this method. + // 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: + // dojo.html.set(node, "some string"); + // dojo.html.set(node, contentNode, {options}); + // dojo.html.set(node, myNode.childNodes, {options}); + if(undefined == cont){ + console.warn("dojo.html.set: no cont argument provided, using empty string"); + cont = ""; + } + if(!params){ + // simple and fast + return dojo.html._setNodeContent(node, cont, true); + }else{ + // more options but slower + // note the arguments are reversed in order, to match the convention for instantiation via the parser + var op = new dojo.html._ContentSetter(dojo.mixin( + params, + { content: cont, node: node } + )); + return op.set(); + } + }; })(); + } |