dom-construct.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. define(["exports", "./_base/kernel", "./sniff", "./_base/window", "./dom", "./dom-attr"],
  2. function(exports, dojo, has, win, dom, attr){
  3. // module:
  4. // dojo/dom-construct
  5. // summary:
  6. // This module defines the core dojo DOM construction API.
  7. // TODOC: summary not showing up in output, see https://github.com/csnover/js-doc-parse/issues/42
  8. // support stuff for toDom()
  9. var tagWrap = {
  10. option: ["select"],
  11. tbody: ["table"],
  12. thead: ["table"],
  13. tfoot: ["table"],
  14. tr: ["table", "tbody"],
  15. td: ["table", "tbody", "tr"],
  16. th: ["table", "thead", "tr"],
  17. legend: ["fieldset"],
  18. caption: ["table"],
  19. colgroup: ["table"],
  20. col: ["table", "colgroup"],
  21. li: ["ul"]
  22. },
  23. reTag = /<\s*([\w\:]+)/,
  24. masterNode = {}, masterNum = 0,
  25. masterName = "__" + dojo._scopeName + "ToDomId";
  26. // generate start/end tag strings to use
  27. // for the injection for each special tag wrap case.
  28. for(var param in tagWrap){
  29. if(tagWrap.hasOwnProperty(param)){
  30. var tw = tagWrap[param];
  31. tw.pre = param == "option" ? '<select multiple="multiple">' : "<" + tw.join("><") + ">";
  32. tw.post = "</" + tw.reverse().join("></") + ">";
  33. // the last line is destructive: it reverses the array,
  34. // but we don't care at this point
  35. }
  36. }
  37. var html5domfix;
  38. if(has("ie") <= 8){
  39. html5domfix = function(doc){
  40. doc.__dojo_html5_tested = "yes";
  41. var div = create('div', {innerHTML: "<nav>a</nav>", style: {visibility: "hidden"}}, doc.body);
  42. if(div.childNodes.length !== 1){
  43. ('abbr article aside audio canvas details figcaption figure footer header ' +
  44. 'hgroup mark meter nav output progress section summary time video').replace(
  45. /\b\w+\b/g, function(n){
  46. doc.createElement(n);
  47. }
  48. );
  49. }
  50. destroy(div);
  51. }
  52. }
  53. function _insertBefore(/*DomNode*/ node, /*DomNode*/ ref){
  54. var parent = ref.parentNode;
  55. if(parent){
  56. parent.insertBefore(node, ref);
  57. }
  58. }
  59. function _insertAfter(/*DomNode*/ node, /*DomNode*/ ref){
  60. // summary:
  61. // Try to insert node after ref
  62. var parent = ref.parentNode;
  63. if(parent){
  64. if(parent.lastChild == ref){
  65. parent.appendChild(node);
  66. }else{
  67. parent.insertBefore(node, ref.nextSibling);
  68. }
  69. }
  70. }
  71. exports.toDom = function toDom(frag, doc){
  72. // summary:
  73. // instantiates an HTML fragment returning the corresponding DOM.
  74. // frag: String
  75. // the HTML fragment
  76. // doc: DocumentNode?
  77. // optional document to use when creating DOM nodes, defaults to
  78. // dojo/_base/window.doc if not specified.
  79. // returns:
  80. // Document fragment, unless it's a single node in which case it returns the node itself
  81. // example:
  82. // Create a table row:
  83. // | require(["dojo/dom-construct"], function(domConstruct){
  84. // | var tr = domConstruct.toDom("<tr><td>First!</td></tr>");
  85. // | });
  86. doc = doc || win.doc;
  87. var masterId = doc[masterName];
  88. if(!masterId){
  89. doc[masterName] = masterId = ++masterNum + "";
  90. masterNode[masterId] = doc.createElement("div");
  91. }
  92. if(has("ie") <= 8){
  93. if(!doc.__dojo_html5_tested && doc.body){
  94. html5domfix(doc);
  95. }
  96. }
  97. // make sure the frag is a string.
  98. frag += "";
  99. // find the starting tag, and get node wrapper
  100. var match = frag.match(reTag),
  101. tag = match ? match[1].toLowerCase() : "",
  102. master = masterNode[masterId],
  103. wrap, i, fc, df;
  104. if(match && tagWrap[tag]){
  105. wrap = tagWrap[tag];
  106. master.innerHTML = wrap.pre + frag + wrap.post;
  107. for(i = wrap.length; i; --i){
  108. master = master.firstChild;
  109. }
  110. }else{
  111. master.innerHTML = frag;
  112. }
  113. // one node shortcut => return the node itself
  114. if(master.childNodes.length == 1){
  115. return master.removeChild(master.firstChild); // DOMNode
  116. }
  117. // return multiple nodes as a document fragment
  118. df = doc.createDocumentFragment();
  119. while((fc = master.firstChild)){ // intentional assignment
  120. df.appendChild(fc);
  121. }
  122. return df; // DocumentFragment
  123. };
  124. exports.place = function place(node, refNode, position){
  125. // summary:
  126. // Attempt to insert node into the DOM, choosing from various positioning options.
  127. // Returns the first argument resolved to a DOM node.
  128. // node: DOMNode|DocumentFragment|String
  129. // id or node reference, or HTML fragment starting with "<" to place relative to refNode
  130. // refNode: DOMNode|String
  131. // id or node reference to use as basis for placement
  132. // position: String|Number?
  133. // string noting the position of node relative to refNode or a
  134. // number indicating the location in the childNodes collection of refNode.
  135. // Accepted string values are:
  136. //
  137. // - before
  138. // - after
  139. // - replace
  140. // - only
  141. // - first
  142. // - last
  143. //
  144. // "first" and "last" indicate positions as children of refNode, "replace" replaces refNode,
  145. // "only" replaces all children. position defaults to "last" if not specified
  146. // returns: DOMNode
  147. // Returned values is the first argument resolved to a DOM node.
  148. //
  149. // .place() is also a method of `dojo/NodeList`, allowing `dojo/query` node lookups.
  150. // example:
  151. // Place a node by string id as the last child of another node by string id:
  152. // | require(["dojo/dom-construct"], function(domConstruct){
  153. // | domConstruct.place("someNode", "anotherNode");
  154. // | });
  155. // example:
  156. // Place a node by string id before another node by string id
  157. // | require(["dojo/dom-construct"], function(domConstruct){
  158. // | domConstruct.place("someNode", "anotherNode", "before");
  159. // | });
  160. // example:
  161. // Create a Node, and place it in the body element (last child):
  162. // | require(["dojo/dom-construct", "dojo/_base/window"
  163. // | ], function(domConstruct, win){
  164. // | domConstruct.place("<div></div>", win.body());
  165. // | });
  166. // example:
  167. // Put a new LI as the first child of a list by id:
  168. // | require(["dojo/dom-construct"], function(domConstruct){
  169. // | domConstruct.place("<li></li>", "someUl", "first");
  170. // | });
  171. refNode = dom.byId(refNode);
  172. if(typeof node == "string"){ // inline'd type check
  173. node = /^\s*</.test(node) ? exports.toDom(node, refNode.ownerDocument) : dom.byId(node);
  174. }
  175. if(typeof position == "number"){ // inline'd type check
  176. var cn = refNode.childNodes;
  177. if(!cn.length || cn.length <= position){
  178. refNode.appendChild(node);
  179. }else{
  180. _insertBefore(node, cn[position < 0 ? 0 : position]);
  181. }
  182. }else{
  183. switch(position){
  184. case "before":
  185. _insertBefore(node, refNode);
  186. break;
  187. case "after":
  188. _insertAfter(node, refNode);
  189. break;
  190. case "replace":
  191. refNode.parentNode.replaceChild(node, refNode);
  192. break;
  193. case "only":
  194. exports.empty(refNode);
  195. refNode.appendChild(node);
  196. break;
  197. case "first":
  198. if(refNode.firstChild){
  199. _insertBefore(node, refNode.firstChild);
  200. break;
  201. }
  202. // else fallthrough...
  203. default: // aka: last
  204. refNode.appendChild(node);
  205. }
  206. }
  207. return node; // DomNode
  208. };
  209. var create = exports.create = function create(/*DOMNode|String*/ tag, /*Object*/ attrs, /*DOMNode|String?*/ refNode, /*String?*/ pos){
  210. // summary:
  211. // Create an element, allowing for optional attribute decoration
  212. // and placement.
  213. // description:
  214. // A DOM Element creation function. A shorthand method for creating a node or
  215. // a fragment, and allowing for a convenient optional attribute setting step,
  216. // as well as an optional DOM placement reference.
  217. //
  218. // Attributes are set by passing the optional object through `dojo/dom-attr.set`.
  219. // See `dojo/dom-attr.set` for noted caveats and nuances, and API if applicable.
  220. //
  221. // Placement is done via `dojo/dom-construct.place`, assuming the new node to be
  222. // the action node, passing along the optional reference node and position.
  223. // tag: DOMNode|String
  224. // A string of the element to create (eg: "div", "a", "p", "li", "script", "br"),
  225. // or an existing DOM node to process.
  226. // attrs: Object
  227. // An object-hash of attributes to set on the newly created node.
  228. // Can be null, if you don't want to set any attributes/styles.
  229. // See: `dojo/dom-attr.set` for a description of available attributes.
  230. // refNode: DOMNode|String?
  231. // Optional reference node. Used by `dojo/dom-construct.place` to place the newly created
  232. // node somewhere in the dom relative to refNode. Can be a DomNode reference
  233. // or String ID of a node.
  234. // pos: String?
  235. // Optional positional reference. Defaults to "last" by way of `dojo/domConstruct.place`,
  236. // though can be set to "first","after","before","last", "replace" or "only"
  237. // to further control the placement of the new node relative to the refNode.
  238. // 'refNode' is required if a 'pos' is specified.
  239. // example:
  240. // Create a DIV:
  241. // | require(["dojo/dom-construct"], function(domConstruct){
  242. // | var n = domConstruct.create("div");
  243. // | });
  244. //
  245. // example:
  246. // Create a DIV with content:
  247. // | require(["dojo/dom-construct"], function(domConstruct){
  248. // | var n = domConstruct.create("div", { innerHTML:"<p>hi</p>" });
  249. // | });
  250. //
  251. // example:
  252. // Place a new DIV in the BODY, with no attributes set
  253. // | require(["dojo/dom-construct", "dojo/_base/window"], function(domConstruct, win){
  254. // | var n = domConstruct.create("div", null, win.body());
  255. // | });
  256. //
  257. // example:
  258. // Create an UL, and populate it with LI's. Place the list as the first-child of a
  259. // node with id="someId":
  260. // | require(["dojo/dom-construct", "dojo/_base/array"],
  261. // | function(domConstruct, arrayUtil){
  262. // | var ul = domConstruct.create("ul", null, "someId", "first");
  263. // | var items = ["one", "two", "three", "four"];
  264. // | arrayUtil.forEach(items, function(data){
  265. // | domConstruct.create("li", { innerHTML: data }, ul);
  266. // | });
  267. // | });
  268. //
  269. // example:
  270. // Create an anchor, with an href. Place in BODY:
  271. // | require(["dojo/dom-construct", "dojo/_base/window"], function(domConstruct, win){
  272. // | domConstruct.create("a", { href:"foo.html", title:"Goto FOO!" }, win.body());
  273. // | });
  274. var doc = win.doc;
  275. if(refNode){
  276. refNode = dom.byId(refNode);
  277. doc = refNode.ownerDocument;
  278. }
  279. if(typeof tag == "string"){ // inline'd type check
  280. tag = doc.createElement(tag);
  281. }
  282. if(attrs){ attr.set(tag, attrs); }
  283. if(refNode){ exports.place(tag, refNode, pos); }
  284. return tag; // DomNode
  285. };
  286. function _empty(/*DomNode*/ node){
  287. // TODO: remove this if() block in 2.0 when we no longer have to worry about IE memory leaks,
  288. // and then uncomment the emptyGrandchildren() test case from html.html.
  289. // Note that besides fixing #16957, using removeChild() is actually faster than setting node.innerHTML,
  290. // see http://jsperf.com/clear-dom-node.
  291. if("innerHTML" in node){
  292. try{
  293. // fast path
  294. node.innerHTML = "";
  295. return;
  296. }catch(e){
  297. // innerHTML is readOnly (e.g. TABLE (sub)elements in quirks mode)
  298. // Fall through (saves bytes)
  299. }
  300. }
  301. // SVG/strict elements don't support innerHTML
  302. for(var c; c = node.lastChild;){ // intentional assignment
  303. node.removeChild(c);
  304. }
  305. }
  306. exports.empty = function empty(/*DOMNode|String*/ node){
  307. // summary:
  308. // safely removes all children of the node.
  309. // node: DOMNode|String
  310. // a reference to a DOM node or an id.
  311. // example:
  312. // Destroy node's children byId:
  313. // | require(["dojo/dom-construct"], function(domConstruct){
  314. // | domConstruct.empty("someId");
  315. // | });
  316. _empty(dom.byId(node));
  317. };
  318. function _destroy(/*DomNode*/ node, /*DomNode*/ parent){
  319. // in IE quirks, node.canHaveChildren can be false but firstChild can be non-null (OBJECT/APPLET)
  320. if(node.firstChild){
  321. _empty(node);
  322. }
  323. if(parent){
  324. // removeNode(false) doesn't leak in IE 6+, but removeChild() and removeNode(true) are known to leak under IE 8- while 9+ is TBD.
  325. // In IE quirks mode, PARAM nodes as children of OBJECT/APPLET nodes have a removeNode method that does nothing and
  326. // the parent node has canHaveChildren=false even though removeChild correctly removes the PARAM children.
  327. // In IE, SVG/strict nodes don't have a removeNode method nor a canHaveChildren boolean.
  328. has("ie") && parent.canHaveChildren && "removeNode" in node ? node.removeNode(false) : parent.removeChild(node);
  329. }
  330. }
  331. var destroy = exports.destroy = function destroy(/*DOMNode|String*/ node){
  332. // summary:
  333. // Removes a node from its parent, clobbering it and all of its
  334. // children.
  335. //
  336. // description:
  337. // Removes a node from its parent, clobbering it and all of its
  338. // children. Function only works with DomNodes, and returns nothing.
  339. //
  340. // node: DOMNode|String
  341. // A String ID or DomNode reference of the element to be destroyed
  342. //
  343. // example:
  344. // Destroy a node byId:
  345. // | require(["dojo/dom-construct"], function(domConstruct){
  346. // | domConstruct.destroy("someId");
  347. // | });
  348. node = dom.byId(node);
  349. if(!node){ return; }
  350. _destroy(node, node.parentNode);
  351. };
  352. });