html.js 12 KB

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