Shaven

2.1.0

Get rid of your old-fashioned face furniture and build sleek templates!

What is Shaven?

Shaven is a DOM building utility and template engine based on JsonML. This simple markup language lets you easily represent XML/HTML with JSON and plain JavaScript objects. Templates are therefore simply a tree of nested arrays.

['#demo',
['h1#logo', 'Test', {title: 'Test'}],
['p', 'Some example text'],
['ul#list.bullets',
['li', 'item1'],
['li.active', 'item2'],
['li',
['a', 'item3', {href: '#'}],
],
],
]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

compiles to

<div id="demo">
<h1 id="logo" title="Test">Test</h1>
<p>Some example text</p>
<ul id="list" class="bullets">
<li>item1</li>
<li class="active">item2</li>
<li>
<a href="#">item3</a>
</li>
</ul>
</div>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

The first element of the array is the root html element which is going to contain all the child-elements. Those are arrays themselves and are structured as follows:

Installation

On the server you can install Shaven with npm:

npm install shaven
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Then require it in your scripts like this:

import shaven from 'shaven'
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

In the browser use a bundler or load the pre-compiled version of Shaven from npmjs.com/package/shaven

<script src="shaven.js"></script>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Now you can start using it by passing an array to Shaven:

const shavenObject = shaven(['p', 'some text'])
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Insert Methods

There are three different methods to add a DOM-fragment to a document.

1. Inject it straight into the DOM:

shaven(
[document.body,
['div#demo',
],
]
)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

2. Assign the Shaven-object to a variable and append the fragment later.

To get the HTML-fragment from the Shaven-object reference it with .rootElement.

const shavenObject = shaven(
['#demo',
]
)
document.body.appendChild(shavenObject.rootElement)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Both compiles to:

<body>
<div id="demo">
</div>
</body>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

3. Set the innerHTML of an element.

To get the HTML string of a Shaven-object simply call the toString() method, convert the object to a String with String(shavenObject) or the easiest way - add it to another string.

console.log('The HTML for the object is ' + shavenObject)
document.body.innerHTML = shavenObject.toString()
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Syntax Sugar

Tag

When you omit the html-tag it uses the default div tag. So div#container ist the same as #container

['#container', ['p', 'Test']]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

compiles to

<div id="container"><p>Test</p></div>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Id

['p#example', 'example text']
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

compiles to

<p id="example">example text</p>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Class

['p.info', 'example text']
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

compiles to

<p class="info">example text</p>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

References

When an element with an id is created it automatically gets inserted into the corresponding property in the return-object of the shaven function call. Therefore it can get easily referenced later on.

const shavenObject = shaven(
[document.body,
['div#test', 'some text'],
]
)
doSomething(shavenObject.ids.test)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Reference an element without an id by using the $ keyword to assign it a reference-name.

const shavenObject = shaven(
[document.body,
['div$test', 'some text'],
]
)
doSomething(shavenObject.references.test)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Properties

All entries of an object become the attributes of the element. If an object entry is undefined the attribute is set to an empty string. If an entry is null or false the attribute is not created.

Style Attribute

If the style entry is an object it will automatically get compiled to a string. This is especially useful for working with svgs, where inline styles are still frequently used. Values which are false, null or undefined are deleted from the final style-string.

['p', 'Hello', {
class: 'new focus',
'data-lang': 'de',
title: false,
style: {
color: 'red',
display: 'block',
font: null,
},
}]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

compiles to

<p class="new focus" data-lang="de" style="color:red;display:block">
Hello
</p>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Transform Attribute

If a transform attribute is an an array of objects it will get compiled to an SVG transform string.

['svg',
{width: 50, height: 50},
['circle', {
r: 5,
transform: [
{type: 'translate', x: 4, y: 5},
{type: 'skewX', x: 6},
],
}],
]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

compiles to

<svg width="50" height="50">
<circle r="5" transform="translate(4,5) skewX(6)"></circle>
</svg>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Escaping

HTML-strings are escaped by default.

['div', '<p>This is <em>HTML</em></p>']
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

compiles to

<div>&lt;p&gt;This is &lt;em&gt;HTML&lt;/em&gt;&lt;/p&gt;</div>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

To disable escaping add a ! at the end of the element-tag string.

['div!', '<p>This is <em>HTML</em></p>']
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

compiles to

<div><p>This is <em>HTML</em></p></div>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Falsy values

When an element array contains an undefined content-value or no content-value at all, the element will get compiled with an empty body.

When an element contains a null or a false value, the element won't get compiled at all. This is handy for disabling parts of templates or ensuring that no empty container-elements get rendered. (A true value does therefore not change the compilation.)

Subarrays

An array which consists exclusively of subarrays will get merged into the current array

['p', [
['span', 'One'],
['span', 'Two'],
['span', 'Three'],
]]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

compiles to

<p>
<span>One</span>
<span>Two</span>
<span>Three</span>
</p>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Templates

In order to create reusable templates it's best practice to create a function which accepts a data object, inserts the data into the JsonML array and returns it.
Finally iterate over the contacts and use one of the two insertion methods to build the DOM with the contacts.

function contactTemplate (data) {
return ['div.contact',
['h1', data.name],
['dl',
['dt', 'Age:'],
['dd', data.age],
['dt', 'Mobile:'],
['dd', data.mobile],
['dt', 'Email:'],
['dd', data.email],
]
]
}
contacts.forEach(contact =>
shaven([
document.body,
contactTemplate(contact),
])
)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Callback Functions

You can provide a callback function, which will be called after the element has been created, with the element as the only argument. This functionality is especially suited to add event-listeners.

function doSomething (element) {
alert(element + ' was created')
}
shaven(
[document.body,
['div', doSomething],
]
)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Configuration

Shaven can be configured at module scope and settings can be overwritten at each invocation. Available settings are:

{
// Wrap all attribute values in quotes
quoteAttributes: true,
// Character used as quotation mark
quotationMark: '"',
// Escape HTML in text if not explicitly defined otherwise
escapeHTML: true,
// Convert array of transform objects to transform-string
convertTransformArray: true,
// Default namespace
namespace: 'xhtml',
// Automatically identify namespace
autoNamespacing: true,
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

To overwrite the default settings use setDefaults(). This will be set at the module scope for every invocation of shaven.

import shaven from 'shaven'
shaven.setDefaults({
quoteAttributes: false,
})
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

To overwrite the settings for only one element use the object invocation mode of shaven:

shaven({
quoteAttributes: false,
elementArray: ['p', 'Example text', {title: example}],
})
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

compiles to

<p title=example>Example text</p>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Namespaces

Shaven automatically uses the correct namespaces to create HTML, SVG, and MathML. It is, however, also possible to use a custom namespace to create documents in various other XML-based languages or to overwrite the defaults.

shaven({
namespace: 'http://www.mozilla.org/xbl',
elementArray: ['page',
['vbox', {flex: 1}],
],
})
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

compiles to

<page>
<vbox flex="1"></vbox>
</svg>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Fork me on GitHub