html.js 12 KB

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