Fluent DOM Manipulation in JavaScript[source]
xml
<glacius:metadata> | |
<title>Fluent DOM Manipulation in JavaScript</title> | |
<description>Fluent DOM Manipulation in JavaScript</description> | |
<category>Legacy blog posts</category> | |
<category>Programming</category> | |
<category>JavaScript</category> | |
<category>jQuery</category> | |
</glacius:metadata> | |
<glacius:macro name="legacy blargh banner"> | |
<properties> | |
<originalUrl>https://tmont.com/blargh/2009/11/fluent-dom-manipulation-in-javascript</originalUrl> | |
<originalDate>2009-11-19T09:37:00.000Z</originalDate> | |
</properties> | |
</glacius:macro> | |
<p> | |
Everybody hates the DOM. Except me. I kinda love it. I get a little hit of endorphins every time I | |
type all 23 characters of "document.getElementById". I need a cigarette. | |
</p> | |
<p> | |
Anyway, I found myself dissatisfied with the state of <a href="https://jquery.com/">jQuery's</a> | |
DOM manipulation (hint: there isn't any). You can traverse, and do stuff with events, but there's | |
no API to create elements and then append them to the DOM. So I looked at the jQuery plugins, and | |
found <a href="http://plugins.jquery.com/project/appendDom">this</a>, which I tried and quickly | |
learned to hate. Using JSON to create DOM elements is just as painful as creating them using the | |
native DOM API. | |
</p> | |
<p> | |
So, like any good programmer, I rolled my own. And I called it <strong>FluentDom</strong> (or | |
<strong>$dom</strong> for short). Tested in Firefox, IE and Opera. Licensed under the | |
<a href="http://sam.zoy.org/wtfpl/">WTFPL</a> license. | |
</p> | |
<h2>FluentDom.js</h2> | |
<glacius:code lang="javascript"><![CDATA[ | |
/** | |
* Fluent DOM Manipulation | |
* | |
* @author Tommy Montgomery | |
* @license http://sam.zoy.org/wtfpl/ | |
*/ | |
(function(){ | |
var FluentDom = function(node) { | |
return new FluentDomInternal(node); | |
} | |
FluentDom.create = function(tagName) { | |
var f = new FluentDomInternal(); | |
f.create(tagName); | |
return f; | |
} | |
var FluentDomInternal = function(node) { | |
var root = node || null; | |
this.fluentDom = "1.0"; | |
this.append = function(obj) { | |
if (!root || !root.appendChild) { | |
throw new Error("Cannot append to a non-element"); | |
} | |
var type = typeof(obj); | |
if (type === "object") { | |
if (obj.fluentDom) { | |
root.appendChild(obj.toDom()); | |
} else if (obj.nodeType) { | |
root.appendChild(obj); | |
} else { | |
throw new Error("Invalid argument: not a DOM element or a FluentDom object"); | |
} | |
} else if (type === "string" || type === "number") { | |
root.appendChild(document.createTextNode(obj)); | |
} else { | |
throw new Error("Invalid argument: not an object (you gave me a " + typeof(obj) + ")"); | |
} | |
return this; | |
} | |
this.attr = function(name, value) { | |
if (!root || !root.setAttribute) { | |
throw new Error("Cannot set an attribute on a non-element"); | |
} | |
root.setAttribute(name, value); | |
return this; | |
} | |
this.text = function(text) { | |
return this.append(text); | |
} | |
this.create = function(tagName) { | |
root = document.createElement(tagName); | |
return this; | |
} | |
this.id = function(value) { | |
return this.attr("id", value); | |
} | |
this.title = function(value) { | |
return this.attr("title", value); | |
} | |
this.cls = function(value) { | |
return this.attr("class", value); | |
} | |
this.clear = function() { | |
root = null; | |
return this; | |
} | |
this.toDom = function() { | |
return root; | |
} | |
this.href = function(link) { | |
return this.attr("href", link); | |
} | |
}; | |
window.FluentDom = window.$dom = FluentDom; | |
}()); | |
]]></glacius:code> | |
<h2>Usage</h2> | |
<p> | |
Some sample usage, also showing how to integrate with jQuery: | |
</p> | |
<glacius:code lang="javascript"><![CDATA[ | |
// let's make a list! | |
$dom(document.body).append( | |
$dom.create("ul").id("menu").append( | |
$dom.create("li").cls("menu-item").id("menu-item-1").append( | |
$dom.create("a").text("List Item #1").href("#") | |
) | |
).append( | |
$dom.create("li").cls("menu-item").id("menu-item-2").title("click to toggle").append( | |
$($dom.create("a").text("List Item #2").href("#").toDom()).bind("click", function() { | |
$(this).next("ul").toggle(); | |
return false; | |
}).get(0) | |
).append( | |
$dom.create("ul").append( | |
$dom.create("li").cls("sub-menu-item").id("menu-item-4").append( | |
$dom.create("a").text("Sublist Item #1").href("#") | |
) | |
).append( | |
$dom.create("li").cls("sub-menu-item").id("menu-item-5").append( | |
$dom.create("a").text("Sublist Item #2").href("#") | |
) | |
).append( | |
$dom.create("li").cls("sub-menu-item").id("menu-item-6").append( | |
$dom.create("a").text("Sublist Item #3").href("#") | |
) | |
) | |
) | |
).append( | |
$dom.create("li").cls("menu-item").id("menu-item-3").append( | |
$dom.create("a").text("List Item #3").href("#") | |
) | |
) | |
); | |
]]></glacius:code> | |
<p> | |
Which creates and appends this DOM tree to <code>document.body</code>: | |
</p> | |
<glacius:code lang="html"><![CDATA[ | |
<ul id="menu"> | |
<li class="menu-item" id="menu-item-1"><a href="#">List Item #1</a></li> | |
<li class="menu-item" id="menu-item-2" title="click to toggle"><a href="#">List Item #2</a> | |
<ul> | |
<li class="sub-menu-item" id="menu-item-4"><a href="#">Sublist Item #1</a></li> | |
<li class="sub-menu-item" id="menu-item-5"><a href="#">Sublist Item #2</a></li> | |
<li class="sub-menu-item" id="menu-item-6"><a href="#">Sublist Item #3</a></li> | |
</ul> | |
</li> | |
<li class="menu-item" id="menu-item-3"><a href="#">List Item #3</a></li> | |
</ul> | |
]]></glacius:code> | |
<p> | |
Admittedly, it kind of looks like a lot of code, but it's much easier to read than if you had used | |
the native API. Compare that with what the same implementation would look like if you went native: | |
</p> | |
<h2>Native API Implementation</h2> | |
<glacius:code lang="javascript"><![CDATA[ | |
var ul = document.createElement("ul"); | |
ul.id = "menu"; | |
var li = document.createElement("li"); | |
li.className = "menu-item"; | |
li.id = "menu-item-1"; | |
var a = document.createElement("a"); | |
a.href = "#"; | |
a.appendChild(document.createTextNode("List Item #1")); | |
li.appendChild(a); | |
ul.appendChild(li); | |
li = document.createElement("li"); | |
li.className = "menu-item"; | |
li.id = "menu-item-2"; | |
li.title = "click to toggle"; | |
a = document.createElement("a"); | |
a.href = "#"; | |
a.appendChild(document.createTextNode("List Item #2")); | |
$(a).bind("click", function() { | |
$(this).next("ul").toggle(); | |
return false; | |
}); | |
li.appendChild(a); | |
var subList = document.createElement("ul"); | |
var subItem = document.createElement("li"); | |
subItem.className = "menu-item"; | |
subItem.id = "menu-item-4"; | |
var subLink = document.createElement("a"); | |
subLink.href = "#"; | |
subLink.appendChild(document.createTextNode("Sublist Item #1")); | |
subItem.appendChild(subLink); | |
subList.appendChild(subItem); | |
subItem = document.createElement("li"); | |
subItem.className = "menu-item"; | |
subItem.id = "menu-item-5"; | |
subLink = document.createElement("a"); | |
subLink.href="#"; | |
subLink.appendChild(document.createTextNode("Sublist Item #2")); | |
subItem.appendChild(subLink); | |
subList.appendChild(subItem); | |
subItem = document.createElement("li"); | |
subItem.className = "menu-item"; | |
subItem.id = "menu-item-6"; | |
subLink = document.createElement("a"); | |
subLink.href="#"; | |
subLink.appendChild(document.createTextNode("Sublist Item #3")); | |
subItem.appendChild(subLink); | |
subList.appendChild(subItem); | |
li.appendChild(subList); | |
ul.appendChild(li); | |
li = document.createElement("li"); | |
li.className = "menu-item"; | |
li.id = "menu-item-3"; | |
a = document.createElement("a"); | |
a.href = "#"; | |
a.appendChild(document.createTextNode("List Item #3")); | |
li.appendChild(a); | |
ul.appendChild(li); | |
document.body.appendChild(ul); | |
]]></glacius:code> | |
<p> | |
30 lines vs. 70 lines. Fluent == rad. Tell your friends. | |
</p> |