;(function () {
'use strict';
For all code examples below, please assume that we’ve done the following aliasing — just to keep things a little less verbose.
// Assign to shorter variables.
var _ = DOMBuilder, $body = document.body;
$body.a = $body.appendChild;
Do everything in a localized scope. We’ll expose pieces to the global scope later.
;(function () {
'use strict';
Accepts a string representation of a tag name for the element parameter, and a JSON hash of key-value pairs for the attributes parameter. Returns a self-reference to this by default. Provides methods, described below. You can easily shorten this function name by assigning it to a variable.
// Longer-form
$body.a(_.DOM(
_('p', {
'id':'abc',
'class':['def', 'ghi']
})
));
// or...
// Shorter-form
$body.a(_.DOM(
_('p#abc.def.ghi')
));
This X
variable will be exposed to the global scope as DOMBuilder
.
var X = function (elem, attr) {
Internally to DOMBuilder, we use very short variable names so that we can squeeze the file size down as small as possible using Uglify.
var _ = this;
var d = document;
var dotHashRe = new RegExp(/[\.#]/);
var eqRe = new RegExp(/\[([^\]]*)\]/g);
var key;
var k;
var match;
Set a default internal value
attr = attr || {};
Support CSS/jQuery-style notation for generating elements with IDs, classnames, and simple attributes.
div#myId
p#id.class1.class2
a[href=https://google.com]
function notation() {
if (!dotHashRe.test(elem) && !eqRe.test(elem)) {
return {};
}
var att = {
class: [],
id: ''
};
Collect all of the [k=v]
blocks.
var kvPair = [];
while ((match = eqRe.exec(elem)) !== null) {
kvPair.push(match[1].split('='));
}
elem = elem.replace(eqRe, '');
kvPair.forEach(function (val, idx, arr) {
att[arr[idx][0]] = arr[idx][1];
});
Support CSS/jQuery-style notation for generating elements with IDs and classnames.
var pieces = elem.split(dotHashRe);
var elemType = pieces.shift();
var pos = elemType.length;
var classes = att['class'];
pieces.forEach(function (val, idx, arr) {
if (elem[pos] === '#') {
att.id = val;
} else {
classes.push(val);
}
pos += arr[idx].length + 1;
});
att['class'] = classes;
if (!att['class'].length) {
delete att['class'];
}
if (att['id'] === '') {
delete att['id'];
}
return att;
}
Merge the properties of one object with the properties of a second object. (Internal-only!)
function mergeOptions(o1, o2) {
var o3 = {},
attrname;
for (attrname in o1) {
if (o1.hasOwnProperty(attrname)) {
o3[attrname] = o1[attrname];
}
}
for (attrname in o2) {
if (o2.hasOwnProperty(attrname)) {
o3[attrname] = o2[attrname];
}
}
return o3;
}
Merge options into a conglomo-hash!
attr = mergeOptions(attr, notation());
Construct the element, loop through the list of attributes and add them to the node.
if (dotHashRe.test(elem)) {
_.e = d.createElement(elem.split(dotHashRe).shift());
} else {
_.e = d.createElement(elem);
}
if (attr) {
for (key in attr) {
if (attr.hasOwnProperty(key)) {
if (typeof attr[key] === 'object' && typeof attr[key].length === 'number' && typeof attr[key].splice === 'function') {
attr[key] = attr[key].join(' ');
}
Because of the way that IE works, class names need to be added explicitly via the .className
property instead of using .setAttribute()
.
if (key.toString() === 'class') {
_.e.className = attr[key];
Support data: {}
for data attributes
} else if (key.toString() === 'data') {
for (k in attr[key]) {
if (attr[key].hasOwnProperty(k)) {
_.e.setAttribute('data-' + k, attr[key][k]);
}
}
} else {
_.e.setAttribute(key, attr[key]);
}
}
}
}
The child()
method is used to add children to a parent node, or to add a new tag to a
text string. Accepts one or more child nodes in the form of a DOMBuilder
object or a native
HTMLElement
node (created with document.createElement()
). Multiple child nodes are passed as an
array of objects. Returns a self-reference to this
by default.
$body.a(_.DOM(
_('p#abc.def')._([
_('strong').H('This is bold text.'),
_('em').H('This is italic text.')
])
));
_.child = _._ = function (obj) {
If the object isn’t an array, convert it to an array to maintain a single codepath below.
if (typeof obj !== 'object' || typeof obj.length !== 'number' || typeof obj.splice !== 'function') {
obj = [obj];
}
Loop through the indexed array of children. If the node is a DOMBuilder
object, convert it to
DOM and append it. Otherwise, assume it’s a real DOM node.
for (var i = 0, max = obj.length; i < max; i++) {
if (typeof obj[i] === 'undefined') {
break;
}
if (typeof obj[i].asDOM !== 'undefined') {
_.e.appendChild(obj[i].asDOM());
} else {
_.e.appendChild(obj[i]);
}
}
return _;
};
The html()
method is used for adding text or HTML content to a node. Since it leverages .innerHTML
under the hood, you can pass a string of content to the text parameter. The default behavior is to
append content.
If you’d prefer to replace the existing .innerHTML
content instead, pass a boolean true
to the
replace
parameter. Returns a self-reference to this
by default.
Pass no parameters to read back the node as a string of HTML.
$body.a(_.DOM(
_('p#abc.def')._([
_('strong').H('This is bold text.'),
_('em').H('This is italic text.')
])
.H('Replace the previous nodes with this text', true)
));
_('p#abc.def')._([
_('strong').H('This is bold text.'),
_('em').H('This is italic text.')
]).html()
_.html = _.H = function (str, replace) {
No parameters? Read the value instead. Alias for asHTML().
if (arguments.length === 0) {
return _.asHTML();
}
Determine the default value for replace
.
replace = replace || false;
Set the value with innerHTML.
if (replace) {
_.e.innerHTML = str;
} else {
_.e.innerHTML += str;
}
return _;
};
The text()
method is used for adding plain text content to a node. Since it leverages .textContent
or .innerText
under the hood, you can pass a string of content to the text parameter. The default
behavior is to append content.
If you’d prefer to replace the existing content instead, pass a boolean true
to the replace
parameter. Returns a self-reference to this
by default.
Pass no parameters to read back the node as a string of plain text.
$body.a(_.DOM(
_('p#abc.def')._([
_('strong').H('This is bold text.'),
_('em').H('This is italic text.')
])
.T('Replace the previous nodes with this text', true)
));
_('p#abc.def')._([
_('strong').H('This is bold text.'),
_('em').H('This is italic text.')
]).text()
_.text = _.T = function (str) {
No parameters? Read the value instead. Alias for asText().
if (arguments.length === 0) {
return _.asText();
}
Set the value
if (_.e.innerText) {
_.e.innerText = str;
} else {
var text = document.createTextNode(str);
_.e.appendChild(text);
}
return _;
};
Returns a real DOM node for use with the standard JavaScript DOM methods. When DOMBuilder objects
are passed to the child method, asDOM()
is optional. It is only required when it’s the last method
in the chain while being passed into a real JavaScript DOM node.
$body.a(
_('p#abc.def')._([
_('strong').H('This is bold text.'),
_('em').H('This is italic text.')
]).dom()
);
_.asDOM = _.dom = function () {
return _.e;
};
Returns the DOM nodes as a string of HTML. It’s as simple as that.
var id = document.getElementById('id');
id.innerHTML = _('p#abc.def').H('This is my text.').asHTML();
_.asHTML = function () {
var t = d.createElement('div');
t.appendChild(_.e);
return t.innerHTML;
};
Returns the DOM nodes as a string of plain text. It’s as simple as that.
var id = document.getElementById('id');
id.innerHTML = _('p#abc.def').H('This is my text.').asText();
_.asText = function () {
var t = d.createElement('div');
t.appendChild(_.e);
if (t.innerText) {
return t.innerText;
} else if (t.textContent) {
return t.textContent;
}
};
return _;
};
Pre-instantiate the class on each call so that you never need to use new
.
window.DOMBuilder = function (elem, attr) {
return new X(elem, attr);
};
This is the function that all other DOMBuilder
nodes are passed into, as it returns directly into the
appendChild()
method. For a single DOMBuilder
chain, this is interchangable with the .asDOM()
method (although they’re used differently).
If you are passing in multiple sibling nodes without a parent, DOM()
leverages
Document Fragments to substantially speed up the process
of writing multiple nodes to the live DOM.
$body.a(_.DOM([
_('p#abc.def').H('This is my text.'),
_('p').H('Something simpler.'),
_('p').H('Let\'s add a third paragraph, for kicks.')
]));
window.DOMBuilder.DOM = function (nodes) {
Create a document fragment. Grab and loop through the in-memory DOM nodes, and move them to the
var f = document.createDocumentFragment(),
n = new X('div')._(nodes).dom().childNodes;
while (n.length) {
f.appendChild(n[0]);
}
Return the Document Fragment to the calling method (presumably .appendChild()
).
return f;
};
})();