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>