You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
12 KiB
376 lines
12 KiB
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/_base/window.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: |
|
// | require(["dojo/dom-construct"], function(domConstruct){ |
|
// | var tr = domConstruct.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(node, refNode, 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|DocumentFragment|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: |
|
// | require(["dojo/dom-construct"], function(domConstruct){ |
|
// | domConstruct.place("someNode", "anotherNode"); |
|
// | }); |
|
// example: |
|
// Place a node by string id before another node by string id |
|
// | require(["dojo/dom-construct"], function(domConstruct){ |
|
// | domConstruct.place("someNode", "anotherNode", "before"); |
|
// | }); |
|
// example: |
|
// Create a Node, and place it in the body element (last child): |
|
// | require(["dojo/dom-construct", "dojo/_base/window" |
|
// | ], function(domConstruct, win){ |
|
// | domConstruct.place("<div></div>", win.body()); |
|
// | }); |
|
// example: |
|
// Put a new LI as the first child of a list by id: |
|
// | require(["dojo/dom-construct"], function(domConstruct){ |
|
// | domConstruct.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/dom-attr.set`. |
|
// See `dojo/dom-attr.set` for noted caveats and nuances, and API if applicable. |
|
// |
|
// Placement is done via `dojo/dom-construct.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/dom-attr.set` for a description of available attributes. |
|
// refNode: DOMNode|String? |
|
// Optional reference node. Used by `dojo/dom-construct.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/domConstruct.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: |
|
// | require(["dojo/dom-construct"], function(domConstruct){ |
|
// | var n = domConstruct.create("div"); |
|
// | }); |
|
// |
|
// example: |
|
// Create a DIV with content: |
|
// | require(["dojo/dom-construct"], function(domConstruct){ |
|
// | var n = domConstruct.create("div", { innerHTML:"<p>hi</p>" }); |
|
// | }); |
|
// |
|
// example: |
|
// Place a new DIV in the BODY, with no attributes set |
|
// | require(["dojo/dom-construct", "dojo/_base/window"], function(domConstruct, win){ |
|
// | var n = domConstruct.create("div", null, win.body()); |
|
// | }); |
|
// |
|
// example: |
|
// Create an UL, and populate it with LI's. Place the list as the first-child of a |
|
// node with id="someId": |
|
// | require(["dojo/dom-construct", "dojo/_base/array"], |
|
// | function(domConstruct, arrayUtil){ |
|
// | var ul = domConstruct.create("ul", null, "someId", "first"); |
|
// | var items = ["one", "two", "three", "four"]; |
|
// | arrayUtil.forEach(items, function(data){ |
|
// | domConstruct.create("li", { innerHTML: data }, ul); |
|
// | }); |
|
// | }); |
|
// |
|
// example: |
|
// Create an anchor, with an href. Place in BODY: |
|
// | require(["dojo/dom-construct", "dojo/_base/window"], function(domConstruct, win){ |
|
// | domConstruct.create("a", { href:"foo.html", title:"Goto FOO!" }, win.body()); |
|
// | }); |
|
|
|
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){ |
|
// TODO: remove this if() block in 2.0 when we no longer have to worry about IE memory leaks, |
|
// and then uncomment the emptyGrandchildren() test case from html.html. |
|
// Note that besides fixing #16957, using removeChild() is actually faster than setting node.innerHTML, |
|
// see http://jsperf.com/clear-dom-node. |
|
if("innerHTML" in node){ |
|
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 |
|
for(var c; c = node.lastChild;){ // intentional assignment |
|
node.removeChild(c); |
|
} |
|
} |
|
|
|
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: |
|
// | require(["dojo/dom-construct"], function(domConstruct){ |
|
// | domConstruct.empty("someId"); |
|
// | }); |
|
|
|
_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: |
|
// | require(["dojo/dom-construct"], function(domConstruct){ |
|
// | domConstruct.destroy("someId"); |
|
// | }); |
|
|
|
node = dom.byId(node); |
|
if(!node){ return; } |
|
_destroy(node, node.parentNode); |
|
}; |
|
});
|
|
|