]> git.wh0rd.org - tt-rss.git/blob - lib/dojo/html.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dojo / html.js
1 /*
2 Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
3 Available via Academic Free License >= 2.1 OR the modified BSD license.
4 see: http://dojotoolkit.org/license for details
5 */
6
7
8 if(!dojo._hasResource["dojo.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9 dojo._hasResource["dojo.html"] = true;
10 dojo.provide("dojo.html");
11 dojo.require("dojo.parser");
12
13 dojo.getObject("html", true, dojo);
14
15 // the parser might be needed..
16 (function(){ // private scope, sort of a namespace
17
18 // idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes
19 var idCounter = 0,
20 d = dojo;
21
22 dojo.html._secureForInnerHtml = function(/*String*/ cont){
23 // summary:
24 // removes !DOCTYPE and title elements from the html string.
25 //
26 // khtml is picky about dom faults, you can't attach a style or <title> node as child of body
27 // must go into head, so we need to cut out those tags
28 // cont:
29 // An html string for insertion into the dom
30 //
31 return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String
32 };
33
34 /*====
35 dojo.html._emptyNode = function(node){
36 // summary:
37 // removes all child nodes from the given node
38 // node: DOMNode
39 // the parent element
40 };
41 =====*/
42 dojo.html._emptyNode = dojo.empty;
43
44 dojo.html._setNodeContent = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont){
45 // summary:
46 // inserts the given content into the given node
47 // node:
48 // the parent element
49 // content:
50 // the content to be set on the parent element.
51 // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
52
53 // always empty
54 d.empty(node);
55
56 if(cont) {
57 if(typeof cont == "string") {
58 cont = d._toDom(cont, node.ownerDocument);
59 }
60 if(!cont.nodeType && d.isArrayLike(cont)) {
61 // handle as enumerable, but it may shrink as we enumerate it
62 for(var startlen=cont.length, i=0; i<cont.length; i=startlen==cont.length ? i+1 : 0) {
63 d.place( cont[i], node, "last");
64 }
65 } else {
66 // pass nodes, documentFragments and unknowns through to dojo.place
67 d.place(cont, node, "last");
68 }
69 }
70
71 // return DomNode
72 return node;
73 };
74
75 // we wrap up the content-setting operation in a object
76 dojo.declare("dojo.html._ContentSetter", null,
77 {
78 // node: DomNode|String
79 // An node which will be the parent element that we set content into
80 node: "",
81
82 // content: String|DomNode|DomNode[]
83 // The content to be placed in the node. Can be an HTML string, a node reference, or a enumerable list of nodes
84 content: "",
85
86 // id: String?
87 // Usually only used internally, and auto-generated with each instance
88 id: "",
89
90 // cleanContent: Boolean
91 // Should the content be treated as a full html document,
92 // and the real content stripped of <html>, <body> wrapper before injection
93 cleanContent: false,
94
95 // extractContent: Boolean
96 // Should the content be treated as a full html document, and the real content stripped of <html>, <body> wrapper before injection
97 extractContent: false,
98
99 // parseContent: Boolean
100 // Should the node by passed to the parser after the new content is set
101 parseContent: false,
102
103 // parserScope: String
104 // Flag passed to parser. Root for attribute names to search for. If scopeName is dojo,
105 // will search for data-dojo-type (or dojoType). For backwards compatibility
106 // reasons defaults to dojo._scopeName (which is "dojo" except when
107 // multi-version support is used, when it will be something like dojo16, dojo20, etc.)
108 parserScope: dojo._scopeName,
109
110 // startup: Boolean
111 // Start the child widgets after parsing them. Only obeyed if parseContent is true.
112 startup: true,
113
114 // lifecyle methods
115 constructor: function(/* Object */params, /* String|DomNode */node){
116 // summary:
117 // Provides a configurable, extensible object to wrap the setting on content on a node
118 // call the set() method to actually set the content..
119
120 // the original params are mixed directly into the instance "this"
121 dojo.mixin(this, params || {});
122
123 // give precedence to params.node vs. the node argument
124 // and ensure its a node, not an id string
125 node = this.node = dojo.byId( this.node || node );
126
127 if(!this.id){
128 this.id = [
129 "Setter",
130 (node) ? node.id || node.tagName : "",
131 idCounter++
132 ].join("_");
133 }
134 },
135 set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){
136 // summary:
137 // front-end to the set-content sequence
138 // cont:
139 // An html string, node or enumerable list of nodes for insertion into the dom
140 // If not provided, the object's content property will be used
141 if(undefined !== cont){
142 this.content = cont;
143 }
144 // in the re-use scenario, set needs to be able to mixin new configuration
145 if(params){
146 this._mixin(params);
147 }
148
149 this.onBegin();
150 this.setContent();
151 this.onEnd();
152
153 return this.node;
154 },
155 setContent: function(){
156 // summary:
157 // sets the content on the node
158
159 var node = this.node;
160 if(!node) {
161 // can't proceed
162 throw new Error(this.declaredClass + ": setContent given no node");
163 }
164 try{
165 node = dojo.html._setNodeContent(node, this.content);
166 }catch(e){
167 // check if a domfault occurs when we are appending this.errorMessage
168 // like for instance if domNode is a UL and we try append a DIV
169
170 // FIXME: need to allow the user to provide a content error message string
171 var errMess = this.onContentError(e);
172 try{
173 node.innerHTML = errMess;
174 }catch(e){
175 console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e);
176 }
177 }
178 // always put back the node for the next method
179 this.node = node; // DomNode
180 },
181
182 empty: function() {
183 // summary
184 // cleanly empty out existing content
185
186 // destroy any widgets from a previous run
187 // NOTE: if you dont want this you'll need to empty
188 // the parseResults array property yourself to avoid bad things happenning
189 if(this.parseResults && this.parseResults.length) {
190 dojo.forEach(this.parseResults, function(w) {
191 if(w.destroy){
192 w.destroy();
193 }
194 });
195 delete this.parseResults;
196 }
197 // this is fast, but if you know its already empty or safe, you could
198 // override empty to skip this step
199 dojo.html._emptyNode(this.node);
200 },
201
202 onBegin: function(){
203 // summary
204 // Called after instantiation, but before set();
205 // It allows modification of any of the object properties
206 // - including the node and content provided - before the set operation actually takes place
207 // This default implementation checks for cleanContent and extractContent flags to
208 // optionally pre-process html string content
209 var cont = this.content;
210
211 if(dojo.isString(cont)){
212 if(this.cleanContent){
213 cont = dojo.html._secureForInnerHtml(cont);
214 }
215
216 if(this.extractContent){
217 var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
218 if(match){ cont = match[1]; }
219 }
220 }
221
222 // clean out the node and any cruft associated with it - like widgets
223 this.empty();
224
225 this.content = cont;
226 return this.node; /* DomNode */
227 },
228
229 onEnd: function(){
230 // summary
231 // Called after set(), when the new content has been pushed into the node
232 // It provides an opportunity for post-processing before handing back the node to the caller
233 // This default implementation checks a parseContent flag to optionally run the dojo parser over the new content
234 if(this.parseContent){
235 // populates this.parseResults if you need those..
236 this._parse();
237 }
238 return this.node; /* DomNode */
239 },
240
241 tearDown: function(){
242 // summary
243 // manually reset the Setter instance if its being re-used for example for another set()
244 // description
245 // tearDown() is not called automatically.
246 // In normal use, the Setter instance properties are simply allowed to fall out of scope
247 // but the tearDown method can be called to explicitly reset this instance.
248 delete this.parseResults;
249 delete this.node;
250 delete this.content;
251 },
252
253 onContentError: function(err){
254 return "Error occured setting content: " + err;
255 },
256
257 _mixin: function(params){
258 // mix properties/methods into the instance
259 // TODO: the intention with tearDown is to put the Setter's state
260 // back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params)
261 // so we could do something here to move the original properties aside for later restoration
262 var empty = {}, key;
263 for(key in params){
264 if(key in empty){ continue; }
265 // TODO: here's our opportunity to mask the properties we dont consider configurable/overridable
266 // .. but history shows we'll almost always guess wrong
267 this[key] = params[key];
268 }
269 },
270 _parse: function(){
271 // summary:
272 // runs the dojo parser over the node contents, storing any results in this.parseResults
273 // Any errors resulting from parsing are passed to _onError for handling
274
275 var rootNode = this.node;
276 try{
277 // store the results (widgets, whatever) for potential retrieval
278 var inherited = {};
279 dojo.forEach(["dir", "lang", "textDir"], function(name){
280 if(this[name]){
281 inherited[name] = this[name];
282 }
283 }, this);
284 this.parseResults = dojo.parser.parse({
285 rootNode: rootNode,
286 noStart: !this.startup,
287 inherited: inherited,
288 scope: this.parserScope
289 });
290 }catch(e){
291 this._onError('Content', e, "Error parsing in _ContentSetter#"+this.id);
292 }
293 },
294
295 _onError: function(type, err, consoleText){
296 // summary:
297 // shows user the string that is returned by on[type]Error
298 // overide/implement on[type]Error and return your own string to customize
299 var errText = this['on' + type + 'Error'].call(this, err);
300 if(consoleText){
301 console.error(consoleText, err);
302 }else if(errText){ // a empty string won't change current content
303 dojo.html._setNodeContent(this.node, errText, true);
304 }
305 }
306 }); // end dojo.declare()
307
308 dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){
309 // summary:
310 // inserts (replaces) the given content into the given node. dojo.place(cont, node, "only")
311 // may be a better choice for simple HTML insertion.
312 // description:
313 // Unless you need to use the params capabilities of this method, you should use
314 // dojo.place(cont, node, "only"). dojo.place() has more robust support for injecting
315 // an HTML string into the DOM, but it only handles inserting an HTML string as DOM
316 // elements, or inserting a DOM node. dojo.place does not handle NodeList insertions
317 // or the other capabilities as defined by the params object for this method.
318 // node:
319 // the parent element that will receive the content
320 // cont:
321 // the content to be set on the parent element.
322 // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
323 // params:
324 // Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter
325 // example:
326 // A safe string/node/nodelist content replacement/injection with hooks for extension
327 // Example Usage:
328 // dojo.html.set(node, "some string");
329 // dojo.html.set(node, contentNode, {options});
330 // dojo.html.set(node, myNode.childNodes, {options});
331 if(undefined == cont){
332 console.warn("dojo.html.set: no cont argument provided, using empty string");
333 cont = "";
334 }
335 if(!params){
336 // simple and fast
337 return dojo.html._setNodeContent(node, cont, true);
338 }else{
339 // more options but slower
340 // note the arguments are reversed in order, to match the convention for instantiation via the parser
341 var op = new dojo.html._ContentSetter(dojo.mixin(
342 params,
343 { content: cont, node: node }
344 ));
345 return op.set();
346 }
347 };
348 })();
349
350 }