loot.js $dom

Something I’m pretty happy with out of my loot.js project is the $dom function. I saw this post on Hacker News by Neil Jenkins about a dom constructor $el funciton that used a css selector to define the tag class and id, an object to define other properties on that tag and an array for the children. I thought this was awesome and started using it but wanted to eliminate the function calls for each node and also wanted to be able to store a complex dom structure and later use it with the function. This lead me down the path of something much like hiccup, although I didn’t learn about hiccup until after I had nearly finished $dom it seems to be very similar. One final requirement I had was that I wanted to be able to produce dom structures AND output htmlText in node.js without using jsdom, I will explain how this works later in the post.

Here is a sample of using loot.js to build dom structures.

define a tag with css style selectors

$dom("div");
$dom("div#someId");
$dom("div.someClass");
$dom("div#someId.someClass.someOtherClass");
<div></div>
<div id="someId"></div>
<div class="someClass"></div>
<div id="someId" class="someClass someOtherClass"></div>

optionally add some properties

$dom("input", {type: "text", value:"foo"});
<input type="text" value="foo">

innerText and innerHTML

$dom("div", "some inner text");
$dom("div", "<p>some inner html</p>");
<div>some inner text</div>
<div id="someId"><p>some inner html</p></div>

child nodes can be defined by nesting the syntax in an array

$dom("ul", [
	"li", "this is some inner text"
]);
<ul><li>this is some inner text</li></ul>

still works fine with properties

$dom("select", {name: "mySelect"}, [
	"option", {selected: true}, "this is some inner text"
]);
<select name="mySelect"><option selected="selected">this is some inner text</option></select>

if it looks like a valid html tag/selector it will become a sibling, otherwise it is treated as text and will become inner text for the prior node

$dom("ul", ["li", "li", "li"]);
1
<ul><li></li><li></li><li></li></ul>

the space in the 3rd li will actually force it to become innerText to the 2nd li

$dom("ul", ["li","li"," li"]);
<ul><li></li><li> li</li></ul>

the sibling/inner text rules can combined

$dom("ul", [
	"li", "first item",
	"li",
	"li", "third item",
	"li",
	"li", "fifth item"
]);
<ul><li>first item</li><li></li><li>third item</li><li></li><li>fifth item</li></ul>

To facilitate storing these dom structures in variables and then passing them into the $dom function you can wrap everything in an array and pass that one item into the function. These will produce equivalent output.

// two arguments
$dom("ul", ["li","li"," li"]);

// one argument
var myList = ["ul", ["li","li"," li"]];
$dom(myList);

Dom nodes are also valid values.

var li = document.createElement("li");
$dom("ul", [li, li.cloneNode(), li.cloneNode()]);

There is an imperfect check to determine if a given string is a tag or innerText. First it checks for a valid, leading tag name from the following list.

//tags list derived from http://www.w3schools.com/html5/html5_reference.asp
var validTags = "a abbr acronym address applet area article aside audio b base basefont bdi bdo big\
		blockquote body br button canvas caption center cite code col colgroup command datalist\
		dd del details dfn dir div dl dt em embed fieldset figcaption figure font footer\
		form frame frameset h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins keygen kbd\
		label legend li link map mark menu meta meter nav noframes noscript object ol optgroup\
		option output p param pre progress q rp rt ruby s samp script section select small source\
		span strike strong style sub summary sup table tbody td textarea tfoot th thead time title\
		tr track tt u ul var video wbr";

I say this is imperfect because it is possible that you will want to use one of these words as an individual innerText value, and that will not work. Unless you add something to it like an html tag, some whitespace, or more words a singular string from the list will become a dom node.

From this point I started using $dom and added partials and some other helper functions to build web pages with.

$part("docsPage", function(data) {
	return $dom([
		"<!DOCTYPE html>",
		"html", [
			"head", [
				"title", data.title,
				$part("headInclude"),
				"link", {type: "text/css", rel: "stylesheet", href: "/css/docs2.css"},
				"link", {type: "text/css", rel: "stylesheet", href: "/js/prettify/solarized.css"}
			],
			"body", {id: data.pageId}, [

				"div.midNoise", [

					$part('headerInclude'),

					"div.container.span17", [
						"div.row", [
							"div#nav.span3.overlay", data.index,
							"div#doc.span13", data.content
						]
					]
				],


				$part('footerInclude'),

				$part('bottomInclude'),

				$js("/js/prettify/prettify.js"),

				$js("/js/bootstrap-scrollspy.js"),

				$js("/js/docs.js")

			]
		]
	]);
});

somewhere else in the codebase
the $render function renders the partial we defined above by referencing it with the same name.

app.get( "/docs", function(req, res) {
	res.writeHead(200, {"Content-Type": "text/html"});
	res.end($render("docsPage", {
		title: 				"mySite.com",
		pageId: 			"docsPage",
		pageTitle: 			"API Docs v0.1"
	}));
});

whaaaa!? This is pure JavaScript not a templating language. Do you know nothing…

I’ve always felt like templating languages were annoying and still far from some unknown best solution. How many ways will we redefine how to do iteration in some foreign templating language syntax? You hear a lot about how templating systems should be simple and understandable by non-technical persons like designers, whatever that means. If you can understand Adobe Illustrator you should be able to figure out some JavaScript. Also these things end up creating their own complexity that can rival that of a full-blown programming environment. Or there are the warnings of putting too much business logic in your view code and hence we need dumbed down templates to force ourselves to abstain from going hog-wild with the full power of code in the view. Well fooey to all that! I don’t reject these things because the are wrong, I reject them because I don’t want to use them. I’d rather use something in my own projects that keeps me in the code and I’ll use my own judgement as to where to put business vs view stuff thank you very much.

better support for iterator return values

I just added new functionality to the $dom instruction parser to play nicer with inline functions like $map that return a data-structure. First lets look at how you could use $map before.

var colors = ["red", "green", "pink", "blue"];

var colorList = $dom([
	"ul", $map(colors, function(color) {
		return $dom(["li", color])[0];
	})
]);

The $map will return an array which is perfect for defining the children of our ul, but what to return from the iterator? Well in the old way above one thing you could do is another call to $dom or some other node constructor. In our case we use $dom and since it returns an array of nodes you need to pull out the node from the array. This is one thing that $part took care of for you. Otherwise the parser will choke on the inner arrays expecting to first see either a selector or a dom node. But now all of that is irrelevant :-)

Now if the first item in an array is another array that will be a signal to the parser to expand the contents of the inner array and treat it as if it didn’t exist.

var colors = ["red", "green", "pink", "blue"];

var colorList = $dom([
	"ul", $map(colors, function(color) {
		return ["li", color];
	})
]);

In the process the following structure is produced.

var colorList = $dom([
	"ul", [
		["li", "red"],
		["li", "green"],
		["li", "pink"],
		["li", "blue"]
	]
]);

Seeing an array as the first child the parser then concats all the inner arrays together and all is right with the world.

var colorList = $dom([
	"ul", [
		"li", "red",
		"li", "green",
		"li", "pink",
		"li", "blue"
	]
]);

This also applies to objects. In $loot the $map function and it’s friends work on objects transparently, rather than calling $map($values(myObject), iterator) you can just do $map(myObject, iterator). The iterator never needs to change in most cases and the difference is in the return value, the latter returns an object with the same keys as the original object but the values are those returned from the iterator function. This is one of those experimental features of $loot.js


var items = {
	one: "item one",
	two: "item two",
	three: "item three"
};

$dom([
	"ul", $map(items, function(val, key) {
		return ["li."+key, val];
	})
]);

The resulting output would look like this.

$dom([
	"ul", {
		one: ["li.one", "item one"],
		two: ["li.two", "item two"],
		three: ["li.three", "item three"]
	}
]);

Seeing an array as the first value in the object the parser treats it not as attributes for the ul but as a map of children. It concats the array values and VoilĂ !

$dom([
	"ul", [
		"li.one", "item one",
		"li.two", "item two",
		"li.three", "item three"
	]
]);

string mode

FInally I mentioned at the beginning of this post that if you want to switch to string mode you can do that. $dom uses tow other apis, $node and $doc which present the minimal set of functionality for $dom to do its thing with nodes or strings it is basically a tiny, very limited mock dom api. To turn on string mode in the browser just call $doc.useRealDom(“false”); you can switch it back by calling it again with true. In node or any other environment without a document object it will default to the mock dom and the useRealDom method has no effect. You have to be careful with this however because depending on the state of the system some code may work while other code may not. Clearly this is not the optimal solution to this idea of supporting both dom nodes and html strings but it works for now.

but is it fast?

Short answer, NO!

I’ll post some benchmarks but suffice to say its a couple orders of magnitude slower than many of the templating solutions out there. A compiler for this system would need to somehow allow for all the things you can do in javascript while pulling out the static bits as a precompiled template. I’ve begun looking into that but it would require some serious ast parsing. That said there are probably plenty of gains to be had, I have not spent any time optimizing this yet. But when you consider the overhead of the browser and its ability to simply update the dom, the bottleneck is not this $dom parser but the browser itself. So sure you can use some templating language that can build you a million complex tables in a second or two but it will take many times longer for the browser dom to catch up so in practice $dom is viable in the browser for that reason. On the server even if you have a fast templating engine the best bet is to cache heavily if you have lots of server load. In situations where you are sensitive to performance of non-cacheable stuff then you should not use this code otherwise I think its ok.

9 thoughts on “loot.js $dom

  1. this is a cool idea. I tend to agree with you that templates aren’t a great fit for web pages that are being built by actual programmers, as opposed to non-technical designers or content writers.

    I’ve used systems that were similar to this, but in a different context. my last encounter with this idea was with a PHP library (for building social web pages like forums or microblogs) called StatusNet. they had created HTML generator classes for all parts of the DOM and all the templates were generated dynamically by the server in response to http requests. no static content at all actually! there were some advantages to this, but in general it was hard to work with. very verbose and full of boilerplate and it was annoying to integrate it with any client side Javascript.

    your solution looks like it might be a big improvement. obviously the Javasript integration issue is solved by default here. I’m concerned that there is still alot of verbosity and repetition of boilerplate stuff though. How does loot.js compare to a traditional template in terms of number of keystrokes the developer has to type to get the html rendered?

  2. These contain manioc, sweet potatoes and potatoes and as a result you should stay away
    from them.When the carbohydrates don’t generate sugar, the body has to function to come across yet another supply, so some of that stored fat is converted to power and disappears off the physique.Only spread more than the patch at the location exactly where required and it will commence functioning accordingly.Well, you are incorrect!Natrol is the firm behind these carb blockers, and the pills include just a single ingredient; the magical white kidney bean extract.You have to manage correctly so that the patches stay attached to the skin.If it doesn’t work
    for you just send it back for a complete refund.Dietrine is all natural, this
    means you are entirely protected when taking it.

    My page :: wallest.us

  3. Attractive section of content. I just stumbled upon your website and in accession capital
    to assert that I get in fact enjoyed account your
    blog posts. Anyway I’ll be subscribing to your augment and even I achievement you access consistently rapidly.

  4. Nice weblog right here! Also your web site a lot up very fast! What web host are you the use of? Can I get your affiliate hyperlink for your host? I wish my website loaded up as fast as yours lol

  5. On – Conference : This service offers no set-up fee and it has a listed tariff of
    19 cents per minute per person with or without toll-free access.
    Many CNA’s end up going somewhat further within their career by becoming either an LVN, RN,
    or one of those unfortunate other career options. Individuals would probably necessitate
    to end the specialized medical part of this kind of a training program purchased at
    a local clinical establishment.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>