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