can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
377 lines (352 loc) • 12.8 kB
JavaScript
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);
};
});