-
-
Save kerrishotts/2f23cf0ee2aa17ed4e91 to your computer and use it in GitHub Desktop.
h - simple dom templating v 0.1// source http://jsbin.com/filof/6
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/**************************************************************************************************** | |
* | |
* h - Simple DOM templating | |
* | |
* Version 0.1 | |
* Kerri Shotts | |
* MIT License | |
* | |
****************************************************************************************************/ | |
var h = ( function () { | |
/** | |
* internal private method to handle parsing children | |
* and attaching them to their parents | |
* | |
* If the child is a NODE, it is attached directly to the parent as a child | |
* If the child is a FUNCTION, the RESULTS are re-parsed, ultimately to be attached to the parent | |
* as children | |
* If the child is an ARRAY, each element within the array is re-parsed, ultimately to be attached | |
* to the parent as children | |
* | |
* @param {Array|Function|Node} child child to handle and attach | |
* @param {Node} parent parent | |
* | |
*/ | |
function handleChild( child, parent ) { | |
if ( typeof child === "object" ) { | |
if ( child instanceof Array ) { | |
for ( var i = 0, l = child.length; i < l; i++ ) { | |
handleChild( child[ i ], parent ); | |
} | |
} | |
if ( child instanceof Node ) { | |
parent.appendChild( child ); | |
} | |
} | |
if ( typeof child === "function" ) { | |
handleChild( child(), parent ); | |
} | |
} | |
/** | |
* parses an incoming tag into its tag name, id, and class constituents | |
* A tag is of the form "tagName.class#id" or "tagName#id.class". The id and class | |
* are optional. | |
* | |
* @param {string} tag tag to parse | |
* @return {*} Object of the form { tag: tagName, id: id, class: class } | |
*/ | |
function parseTag( tag ) { | |
var tagParts = { | |
tag: "", | |
id: undefined, | |
class: undefined | |
}, | |
hashPos = tag.indexOf( "#" ), | |
dotPos = tag.indexOf( "." ); | |
if ( hashPos < 0 && dotPos < 0 ) { | |
tagParts.tag = tag; | |
return tagParts; | |
} | |
if ( hashPos > 0 && dotPos < 0 ) { | |
tagParts.tag = tag.substr( 0, hashPos ); | |
tagParts.id = tag.substr( hashPos + 1, tag.length ); | |
return tagParts; | |
} | |
if ( dotPos > 0 && hashPos < 0 ) { | |
tagParts.tag = tag.substr( 0, dotPos ); | |
tagParts.class = tag.substr( dotPos + 1, tag.length ); | |
return tagParts; | |
} | |
if ( dotPos > 0 && hashPos > 0 && hashPos < dotPos ) { | |
tagParts.tag = tag.substr( 0, hashPos ); | |
tagParts.id = tag.substr( hashPos + 1, ( dotPos - hashPos ) - 1 ); | |
tagParts.class = tag.substr( dotPos + 1, tag.length ); | |
return tagParts; | |
} | |
if ( dotPos > 0 && hashPos > 0 && dotPos < hashPos ) { | |
tagParts.tag = tag.substr( 0, dotPos ); | |
tagParts.class = tag.substr( dotPos + 1, ( hashPos - dotPos ) - 1 ); | |
tagParts.id = tag.substr( hashPos + 1, tag.length ); | |
return tagParts; | |
} | |
return tagParts; | |
} | |
/** | |
* h templating engine | |
* short for HTML | |
* | |
* Generates a DOM tree (or just a single node) based on a series of method calls | |
* into h. h has one root method (el) that creates all DOM elements, but also has | |
* helper methods for each HTML tag. This means that a UL can be created simply by | |
* calling h.ul. | |
* | |
* Technically there's no such thing as a template using this library, but functions | |
* encapsulating a series of h calls function as an equivalent if properly decoupled | |
* from their surrounds. | |
*/ | |
var h = { | |
/** | |
* Returns a DOM tree containing the requested element and any further child | |
* elements (as extra parameters) | |
* | |
* @param {string} tag tag of the form "tagName.class#id" or "tagName#id.class" | |
* @param {*} tagOptions options for the tag | |
* @param {Array|Function|String} ... children that should be attached | |
* @returns {Node} DOM tree | |
* | |
* tagOptions should be an object consisting of the following optional segments: | |
* | |
* ``` | |
* { | |
* attrs: {...} attributes to add to the element | |
* styles: {...} style attributes to add to the element | |
* on: {...} event handlers to attach to the element | |
* bind: { object:, keyPath: } data binding | |
* store: { object:, keyPath: } store element to object.keyPath | |
* } | |
* ``` | |
* | |
*/ | |
el: function ( tag ) { | |
var e, | |
options, | |
content, | |
tagParts = parseTag( tag ); // parse tag; it should be of the form tag[#id][.class] | |
// create the element; if @DF is used, a document fragment is used instead | |
if ( tagParts.tag !== "@DF" ) { | |
e = document.createElement( tagParts.tag ); | |
} else { | |
e = document.createDocumentFragment(); | |
} | |
// attach the class and id from the tag name, if available | |
if ( tagParts.class !== undefined ) { | |
e.className = tagParts.class; | |
} | |
if ( tagParts.id !== undefined ) { | |
e.setAttribute( "id", tagParts.id ); | |
} | |
// get the arguments as an array, ignoring the first parameter | |
var args = Array.prototype.slice.call( arguments, 1 ); | |
// determine what we've passed in the second/third parameter | |
// if it is an object (but not a node or array), it's a list of | |
// options to attach to the element. If it is a string, it's text | |
// content that should be added using .textContent. | |
// note: we could parse the entire argument list, but that would | |
// a bit absurd. | |
for ( var i = 0; i < 2; i++ ) { | |
if ( typeof args[ 0 ] !== "undefined" ) { | |
if ( typeof args[ 0 ] === "object" ) { | |
// could be a DOM node, an array, or tag options | |
if ( !( args[ 0 ] instanceof Node ) && !( args[ 0 ] instanceof Array ) ) { | |
options = args.shift(); | |
} | |
} | |
if ( typeof args[ 0 ] === "string" ) { | |
// this is text content | |
content = args.shift(); | |
} | |
} | |
} | |
// copy over any attributes and styles in options.attrs and options.style | |
if ( typeof options === "object" ) { | |
// add attributes | |
if ( typeof options.attrs !== "undefined" ) { | |
for ( var attr in options.attrs ) { | |
if ( options.attrs.hasOwnProperty( attr ) ) { | |
e.setAttribute( attr, options.attrs[ attr ] ); | |
} | |
} | |
} | |
// add styles | |
if ( typeof options.styles !== "undefined" ) { | |
for ( var style in options.styles ) { | |
if ( options.styles.hasOwnProperty( style ) ) { | |
e.style[ style ] = options.styles[ style ]; | |
} | |
} | |
} | |
// add event handlers; handler property is expected to be a valid DOM | |
// event, i.e. { "change": function... } or { change: function... } | |
// if the handler is an object, it must be of the form | |
// { handler: function ..., | |
// capture: true/false } | |
if ( typeof options.on !== "undefined" ) { | |
for ( var evt in options.on ) { | |
if ( options.on.hasOwnProperty( evt ) ) { | |
if ( typeof options.on[ evt ] === "function" ) { | |
e.addEventListener( evt, options.on[ evt ].bind( e ), false ); | |
} else { | |
e.addEventListener( evt, options.on[ evt ].handler.bind( e ), typeof options.on[ evt ].capture !== "undefined" ? | |
options.on[ evt ].capture : false ); | |
} | |
} | |
} | |
} | |
// Data binding only occurs if using YASMF's BaseObject for now (built-in pubsub) | |
// along with observable properties | |
// the binding object is of the form { object: objectRef, keyPath: "keyPath" } | |
if ( typeof options.bind !== "undefined" ) { | |
if ( typeof BaseObject !== "undefined" ) { | |
if ( options.bind.object instanceof BaseObject ) { | |
// we have an object that has observable properties | |
options.bind.object.dataBindOn( e, options.bind.keyPath ); | |
// get the current value so it can be displayed | |
content = options.bind.object[ options.bind.keyPath ]; | |
} | |
} | |
} | |
// allow elements to be stored into a context | |
// store must be an object of the form {object:objectRef, keyPath: "keyPath" } | |
if ( typeof options.store !== "undefined" ) { | |
options.store.object[ options.store.keyPath ] = e; | |
} | |
} | |
// if we have content, go ahead and add it | |
// if we're an element that has a value, we attach it to the value | |
// property instead of textContent. If textContent is not available | |
// we use innerText; if that's not available, we complain and do | |
// nothing. Falling back to innerHTML isn't an option, as that's what | |
// we are explicitly trying to avoid. | |
if ( typeof content === "string" || typeof content === "number" ) { | |
if ( typeof e.value !== "undefined" ) { | |
e.value = content; | |
} else { | |
if ( typeof e.textContent !== "undefined" ) { | |
e.textContent = content; | |
} else { | |
if ( typeof e.innerText !== "undefined" ) { | |
e.innerText = content; | |
} else { | |
console.log( "WARNING! This browser doesn't support textContent or innerText." ); | |
} | |
} | |
} | |
} | |
// Handle children; handleChild appends each one to the parent | |
var child, l; | |
for ( i = 0, l = args.length; i < l; i++ ) { | |
//console.log(i); | |
child = args[ i ]; | |
handleChild( child, e ); | |
} | |
// return the element (and associated tree) | |
return e; | |
} | |
}, | |
// create bindings for each HTML element (from: https://developer.mozilla.org/en-US/docs/Web/HTML/Element) | |
els = [ "a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bdi", | |
"bdo", "bgsound", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", | |
"col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dialog", "dir", "div", "dl", | |
"dt", "element", "em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frameset", "h1", "h2", "h3", | |
"h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "iframe", "img", "input", "ins", "isindex", "kbd", | |
"keygen", "label", "legend", "li", "link", "listing", "main", "map", "mark", "marquee", "menu", "menuitem", "meta", "meter", | |
"nav", "nobr", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "picture", "plaintext", | |
"pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "shadow", "small", "source", | |
"spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", | |
"tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr", "xmp" | |
]; | |
els.forEach( function ( el ) { | |
h[ el ] = h.el.bind( h, el ); | |
} ); | |
// bind document fragment too | |
h.DF = h.el.bind( h, "@DF" ); | |
return h; | |
})(); | |
// examples | |
var t = document.getElementById("target"); | |
var n = h.div ( "hello, there", {attrs: { "id": "hi" }} ); | |
t.appendChild (n); | |
// try a more complex example -- this function acts as a template | |
function renderList ( list ) { | |
return h.ul ( | |
list.map ( function (item) { | |
return h.li ( | |
h.el( "span.title", item.title ), | |
h.span( item.description, { attrs: { class: "desc" } } ) | |
); | |
}) | |
); | |
} | |
var aList = [ | |
{ title: "a", description: "first letter" }, | |
{ title: "b", description: "second letter" }, | |
{ title: "z", description: "last letter" } | |
]; | |
var n = renderList ( aList ); | |
console.log ( n.innerHTML ); | |
t.appendChild ( n); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta name="description" content="h - simple dom templating v 0.1" /> | |
<meta charset="utf-8"> | |
<title>Simple Templating Example</title> | |
<script src="h.js" type="text/javascript"></script> | |
</head> | |
<body> | |
<div id="target"> | |
This is where the template is going to render | |
</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment