Tag.js – Pure JavaScript Templating
Tag.js provides a concise syntax for creating HTML by using JS functions. Version 0.0.0. If you use this in production, you're a crazy person. :)
- Outputs HTML as strings, document fragments or jQuery objects.
- Hooks for adding input and output handling, helpers and filters.
- Works beautifully in both the browser and in node.js.
- Only 2 KB (minified and gziped), with no dependencies.
It's mostly an experiment to see if pure JS templating could work, but here's why we like it:
- Modern template files are already mixing JS view logic into the HTML.
- The flexibility of JS allows us to create a nice syntax for generating HTML.
- No more precompiling templates into JS functions. It's already compiled.
Here's an example:
var myTemplate = function() {
div(
h1('Please fill out this form').id('header'),
form(
p('First name:', input({ type: 'text', name: 'firstName' })),
p('Last name:', input({ type: 'text', name: 'lastName' })),
p(input({ type: 'submit', value: 'Save!' }))
),
p('Thanks!')
)
};
tag(myTemplate) // => "<div><h1>...</div>";Installation
Browser
To use tag.js in the browser, download the tag.js file from this repo, or use Bower:
$ bower install tag.jsThen, include the downloaded file in your HTML:
<script src='tag.min.js'></script>Node.js
To use tag.js with node.js, install the library using NPM:
$ npm install tag.jsThen, require it in your code:
var tag = require('tag.js')Usage
Exposing tag functions
By default, tag.js only exposes one variable called tag. You can access all the tag functions as
properties of this variable. You can also pass in a function in which all the tag functions will be available.
If you also pass an object, this will be available as vars in the function scope.
// The div function is only exposed in the function body:
tag(function() { div() }) === '<div></div>'
// Pass inn variables to a template:
var template = function() { p(vars.text) };
tag(template, { text: 'foo' }) => '<p>foo</p>'You may also call tag.expose(), which adds each tag function to the global scope.
The expose method may also take an options object to choose which tags get exposed.
These are the default options:
tag.expose({
// Tags which need closing tags:
open: ['a', 'em', 'strong', 'span', 'p', 'div', 'h1', 'h2', 'h3', 'h4', 'ul', 'ol', 'li', 'form', 'select', 'header', 'nav', 'section', 'article', 'aside', 'footer', 'object', 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'svg'],
// Tags without closing tags:
closed: ['input', 'option', 'br', 'img', 'hr', 'param']
});
tag.div() === div() => '<div></div>'
tag.br() === br() => '<br>'If you only want to change the options (without exposing any extra variables), use tag.config(options).
You may also use tag.reset(options) to create a completely new tag object.
Generating HTML strings
When using tag functions in the global scope (after calling tag.expose), you get a single function in return which can be used to generate the final HTML:
// Generate HTML by calling the function:
div('test')() === '<div>test</div>'
// Generate HTML by using toString:
'' + div('test') === div('test').toString() === '<div>test</div>'
// Generate HTML by using a scope function:
tag(function() { div('test') }) === '<div>test</div>'Creating tags
Passing a tag as an argument to another will create nested tags. Each generated tag has a few methods to add content (append) set attributes (attr) and shorthand methods to set specific attributes.
// Expose all the tags!
tag.expose();
// Creating a tag:
p('hello')() => '<p>hello</p>'
// Nesting of tags:
div(p(1))() => '<div><p>1</p></div>'
// Generating adjecent top level tags:
tag(p(), p())() => '<p></p><p></p>'
// Using append to set content:
p().append(1, 2, 3, span(4))() => '<p>123<span>4</span></p>'
// Using attr to set attributes:
p().attr('lang', 'en')() => '<p lang="en"></p>'
// Pass objects as arguments to set attributes:
p({ id: 1, class: 'a b' })() => '<p id="1" class="a b"></p>'
// Set data attributes:
p(1, 2, 3).data('hello', 'world')() => '<p data-hello="world">123</p>'
// Pass in multiple types of arguments:
p('hello', { lang: 'en' }, span('world'))() => '<p lang="en">hello<span>world</span></p>'
// Using shorthand methods to set attributes:
a().href('/').append('hello')() => '<a href="/">hello</a>'
// Tag content is escaped by default:
div('<hello>')() => '<div><hello></div>'
// Use raw to set unescaped content:
div().raw('<hello>')() => '<div><hello></div>'
// Implicitly create tags with arrays:
ul([1,2,3])() => '<ul><li>1</li><li>2</li><li>3</li>'
// Looping through arrays:
ul(tag.each([1,2,3], li))() => '<ul><li>1</li><li>2</li><li>3</li>'
// Branching tag creation:
div(tag.if(true, p(1), p(2)))() => '<div><p>1</p></div>'Hooks
Input handlers
Input handlers decide what to do with the input arguments
to a tag. Each handler is invoked in order, and can either handle or skip the argument in question.
If a handler returns false, the next handler is invoked.
// Convert all string inputs to uppercase:
tag.use(function(node, input) {
if(typeof input !== 'string') return false;
else return input.toUpperCase();
});
p('foo', 1) => '<p>FOO1</p>'Node methods
Node methods can be used to add methods to tag objects. Each node method gets the current node (tag object), and a parse function, which should be called on any input value to allow the argument handlers to run:
// Create a shorthand method for adding data attributes to tags:
tag.method('data', function (node, parse) {
return function (key, val) {
node.attr('data-' + key, parse(val));
};
});
p().data('foo', 'bar') => '<p data-foo="bar"></p>'Helper functions
You can also add functions to the main tag object, which will be properties of the tag variable:
// Add an unless function to branch tag creation:
tag.fn('unless', function (cond, positive, negative) {
return cond ? negative : positive;
});
div(tag.unless(true, p(1), p(2))) => '<div><p>2</p></div>'Output handlers
By default, tag.js supports generating HTML as strings, document fragments or jQuery objects.
tag.output('string'); // Output HTML as strings (default)
tag.output('dom'); // Output HTML as document fragments (only in the browser)
tag.output('jquery'); // Output HTML as jQuery objects (requires jQuery)You can also register your own output handlers. Registrering a new output handler will also activate that handler. Here's how the jQuery handler is implemented:
// The "meta" variable contains the tag name, its attributes and any child nodes.
// Inner nodes are processed first, ending in the outermost node.
tag.output('jquery', function(meta) {
return jQuery("<" + meta.name + ">")
.attr(meta.attrs)
.append(meta.children);
});