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