Skip to content

Instantly share code, notes, and snippets.

@hns
Created October 29, 2010 19:29
Show Gist options
  • Save hns/654226 to your computer and use it in GitHub Desktop.
Save hns/654226 to your computer and use it in GitHub Desktop.
Simple hiccup implementation in JavaScript
// First argument (or array element for nested arrays) is tag name.
// Second can optionally be an object, in which case it is used to
// supply attributes. All others are added to the tag's body.
html("p", {"class": "foo"}, "hello world", "!")
'<p class="foo">hello world!</p>'
// Shortcuts for denoting id and class attributes
html("p#foo.bar.baz", "hello world!")
'<p id="foo" class="bar baz">hello world!</p>'
// Arrays are used for nested tags
html("p", ["span.highlight", "Important!"], " less important")
'<p><span class="highlight">Important!</span> less important</p>'
// Simple list example
html("ol", ["li", "first"], ["li", "second"])
'<ol><li>first</li><li>second</li></ol>'
// List example with array comprehension - requires JS 1.7
// (but will also combine nicely with Array.map and friends)
html("ul", [["li", item] for each (item in [1, 2, 3])])
'<ul><li>1</li><li>2</li><li>3</li></ul>'
// Pass multiple arrays as arguments to get an element list
// instead of a single element
html(["p", "first paragraph"], ["p", "second paragraph"])
'<p>first paragraph</p><p>second paragraph</p>'
exports.html = function() {
var buffer = [];
build(arguments, buffer);
return buffer.join("");
}
function build(list, buffer) {
var index = 0;
var length = list.length;
if (typeof list[index] === "string") {
var tag = splitTag(list[index++]);
var attr = tag[1];
tag = tag[0];
if (isObject(list[index])) {
mergeAttributes(attr, list[index++]);
}
buffer.push("<", tag);
for (var key in attr) {
buffer.push(" ", key, "=\"", attr[key], "\"");
}
buffer.push(">");
buildRest(list, index, buffer);
buffer.push("</", tag, ">");
} else {
buildRest(list, index, buffer);
}
}
function buildRest(list, index, buffer) {
var length = list.length;
while (index < length) {
var item = list[index++];
if (isArray(item)) {
build(item, buffer);
} else {
buffer.push(item);
}
}
}
function isObject(item) {
return item instanceof Object && item.constructor !== Array;
}
function isArray(item) {
return item instanceof Object && item.constructor === Array;
}
function mergeAttributes(attr1, attr2) {
for (var key in attr2) {
if (!attr1.hasOwnProperty(key)) {
attr1[key] = attr2[key];
} else if (key === "class") {
attr1[key] += " " + attr2[key];
}
}
}
function splitTag(tag) {
var attr = {};
var match = tag.match(/([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?/);
if (match[2]) attr.id = match[2];
if (match[3]) attr["class"] = match[3].replace(/\./g, " ");
return [match[1], attr];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment