Skip to content

Instantly share code, notes, and snippets.

@lazd
Last active August 29, 2015 13:56
Show Gist options
  • Select an option

  • Save lazd/9177694 to your computer and use it in GitHub Desktop.

Select an option

Save lazd/9177694 to your computer and use it in GitHub Desktop.
A sample of template markup from a new, as yet unnamed, template system

DOMly

An insanely fast client-side template system

DOMly uses cloneNode and createElement statements to render templates in the browser up to 7 times faster than doT and Handlebars.

Example

DOMly's syntax is simply HTML with a few special elements and attribute prefixes thrown in, with Mustache-like syntax for variable substitution and method invocation.

<div>
  <h1>Category: {{data.category}}</h1>
    <if data.items.length>
      <ul>
        <foreach data.items>
          <li>
            <h2>{{parent.category}}: {{data.name}}</h2>
            <h3 if-data.sale='class="sale"'>{{data.rice}}</h3>
            <h3>{{formatCount(data.stockCount)}} in stock</h3>
            <button unless-data.stockCount='disabled="disabled"'>Buy now</button>
          </li>
        </foreach>
      </ul>
    <else>
      <p>This category is empty.</p>
    </if>
</div>

Calling a compiled template returns the the root Node or DocumentFragment, ready to be added to the DOM:

var div = template({
  category: 'Main Courses',
  items: [
    {
      name: 'Spicy Steak Tacos',
      sale: true,
      price: '$5.00',
      stockCount: 100
    }
  ]
});

// Add the node to the DOM
document.body.appendChild(div);

Available variables

data

data refers to the current data context as passed to the template. If within a <foreach> or <forin> loop, data refers to the current item.

parent

When within a <foreach> or <forin> loop, parent refers to the data context outside of the loop. This can be chained, resulting in parent.parent referring to the data context outside of two nested loops.

this

this refers to the value of this when executing the template function.

The initial value of this when executing a template is whatever is to the left of the dot:

var obj = {
  template: template
};

// this is obj
obj.template();

You can change the value of this when executing template function by using Function.prototype.call or Function.prototype.bind:

var obj = {
  method: function() {
    return 'Available as this.method()';
  },
  property: 'Available as this.property'
};

var templateData = {
  property: 'Available as this.data'
};

// Render the template with obj as this and templateData as data
var fragment = template.call(obj, data);

someGlobalVariable

All global variables and functions are available within templates.

As properties of the data context and this object must be preceded by data and this respectively, there is no possibility of accidentally using a global variable.

someIterator

An iterator variable, as declared when using <foreach> or <forin> with a named iterator.

Iterators supersede global variables, so you will not be able to access any globals with the same name as an iterator used anywhere in the template.

Statements

Statements take the same form as JavaScript statements, except spaces are not allowed.

Note: Expressions are not currently supported within statements. As such, statements cannot contain &&, ||, +, etc.

variable

Variables can be used as the return value of a statement.

  • data - Substitute the current data context directly
  • data.myProperty - Substitute a property of the current data context
  • this.myProperty - Substitute with a property of this
  • myGlobalVariable - Substitute a global variable
  • myGlobalObject.myProperty - Substitute a property of a globally accessible object

method()

Methods can be invoked as part of a statement.

  • data.myMethod() - Invoke a method of the current data context
  • parent.myMethod() - Invoke a method of the parent data context
  • this.myMethod() - Invoke a method on this
  • myGlobalFunction() - Invoke a globally accessible function
  • myGlobalObject.myMethod() - Invoke a method of a globally accessible object

Invoked methods can be passed any arbitrary arguments. For instance:

myMethod(data.myDataProp,parent.myParentProp,this.myScopeProp,myGlobalVariable,myGlobalObject.myProp)

The above statement would invoke myMethod with the following:

  • The value of the current data context's myDateProp property
  • The value of the parent data context's myParentProp property
  • The value of this's myScopeProp property
  • The value of the global variable myGlobalVariable
  • The value of the myProp property of the globally accessible object, myGlobalObject

Substitutions

{{statement}}

Substitute the return value of statement into the DOM as text.

Substitutions can be made in attribute values or text content:

<button class="{{data.className}}">{{data.label}}</button>

Substitutions are always escaped. It is impossible to inject HTML.

Syntax

<if statement>

Include the contained elements if statement is truthy.

If the value of a data context property is truthy

In this example, we simply test the current data context's enabled property for truthiness, adding the <p> to the DOM if it's truthy.

<if data.enabled>
  <p>{{data.name}} is enabled!</p>
</if>

If the return value of a method is truthy

In this example, the method passesTest is a method of this. We'll pass the current data context to it, and, if passesTest returns a truthy value, we'll add the <p> to the DOM.

<if this.passesTest(data)>
  <p>{{data.name}} passes the test!</p>
</if>

<unless statement>

The opposite of <if statement>.

<else>

Used with <if> and <unless>, evaluated if the statement is falsey.

<if data.enabled>
  <p>{{data.name}} is enabled!</p>
<else>
  <p>{{data.name}} is disabled.</p>
</if>

<foreach statement[,iterator]>

Iterate over the items the of the array returned by statement. The item is available as data.

If iterator is provided, the index of the current item will be available as {{iterator}} for substitution and iterator for method invocation.

Data

{
  "tags": ["hot", "fresh", "new"]
}

Template

<ul>
  <foreach data.tags,tagNumber>
    <li>{{tagNumber}}. {{data}}</li>
  </foreach>
</ul>

Output

<ul>
  <li>0. hot</li>
  <li>1. fresh</li>
  <li>2. new</li>
</ul>

<forin statement[,prop]>

Iterate over the properties of object. The value is available as data.

If prop is provided, the property name will be available as {{prop}} for substitution and prop for method invocation.

Data

{
  "stats": {
    "Spice level": "hot",
    "Vegetarian": "No",
    "Rating": "5"
  }
}

Template

<ul>
  <forin data.stats,stat>
    <li>{{stat}}: {{data}}</li>
  </forin>
</ul>

Output

<ul>
  <li>Spice level: Hot</li>
  <li>Vegetarian: No</li>
  <li>Rating: 5</li>
</ul>

<div if-statement='attr="value"'>

Conditionally sets the attr attribute to value if the return value of statement is truthy.

Use space to separate multiple attributes.

<button if-data.disabled='disabled="disabled" class="disabled"'>Buy</button>

Attributes can contain substitutions as well:

<button if-data.customAttr='{{customAttr.name}}={{customAttr.value}}'>Buy</button>

<div unless-statement='attr="value"'>

The opposite of <div if-statement='attr="value"'>.

<partial SomeNameSpace.someFunction(statement,statement)><partial>

Call SomeNameSpace.someFunction, passing arg1 and arg2. The function must return a DocumentFragment or Node.

If no arguments are passed, the current data context will be passed.

<helper SomeNameSpace.someFunction(statement,statement)>{{statement}} and text</helper>

Call SomeNameSpace.someFunction, passing the evaluated string. The function must return a string which will be inserted as text content.

Text content and statements inside of the node will be evaluated and passed as the last argument to the helper.

<js>

Evaluates the content in place. data will be set to the current data object and can be mutated or re-assigned.

<js>
var i = 10;
while (i-- > 0) {
  data.count = i;
</js>
  <span>{{data.count}}</span>
<js>
}
</js>

handle="handleName"

If the handle attribute is present on any elements in the template, a reference to the element will be assigned as this.handleName.

Statements can also be used within handle names.

Template

<ul handle="list">
  <foreach data.tags,itemNum>
    <li handle="item_{{itemNum}}">{{data}}</li>
  </foreach>
</ul>

Usage

// An object we'll use as the value of this
var obj = {};

// Data for the template
var templateData = {
  name: 'MainList',
  tags: [
    'Tag 1',
    'Tag 2'
  ]
};

// Render the template with obj as this and templateData as data
template.call(obj, templateData);

// For handle names that start with $, references to the jQuery object are available
view.item_0.innerHTML = 'A new Tag 1';
view.item_1.innerHTML = 'A new Tag 2';

If a handle name begins with $, such as $handle, a jQuery object will be stored as $handle and the Node itself will be stored as handle. This is accomplished by passing the node to $, so you can use your own $ function instead of jQuery.

Template precompilation

DOMly parses HTML to generate createElement statements, and as such, it only makes sense if precompiled. You cannot compile DOMly templates in the browser.

Use grunt-DOMly or gulp-DOMly to precompile your templates.

Alternatively, the Node module exports a function that takes template code and options. It returns a function you can serialize and make available for client-side execution however you see fit.

Precompilation with the DOMly module

var compile = require('DOMly');
var fs = require('fs');

var template = compile('<p>My template is {{data.adjective}}!</p>', { stripWhitespace: true });

fs.writeFileSync('template.js', 'var template = '+template.toString()+';');

Usage

<script src="template.js"></script>
<script>
  document.body.appendChild(template({ adjective: 'awesome' }));
</script>

Compiler options

The compiler takes the following options:

stripWhitespace

Type: Boolean
Default: false

If true, meaningless whitespace will be stripped. This provides a large performance boost as less meaningless createTextNode calls are created.

Warning: Meaningful whitespace, such as space between inline tags, will be preserved. However, if your CSS gives display: inline to block elements, whitespace between those elements will still be stripped.

debug

Type: Boolean
Default: false

Dump debug data, including the source file, parsed tree, and compiled function body.

Running the benchmarks

DOMly comes with a set of benchmarks that use karma-benchmark to test real-world browser performance.

npm install
grunt bench

Running the test suite

DOMly is tested with mocha, chai, and jsdom.

npm install
grunt test
Firefox 27.0.0 (Mac OS X 10.9) Complex: ???? at 70418 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Complex: Handlebars at 18904 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Complex: doT at 40229 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Complex: lodash at 23879 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Loops: ???? at 91616 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Loops: Handlebars at 28811 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Loops: doT at 42008 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Loops: lodash at 31892 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Markup: ???? at 60520 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Markup: HTMLbars at 36609 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Markup: Handlebars at 21821 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Markup: doT at 25955 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Markup: lodash at 21110 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Variables: ???? at 96360 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Variables: HTMLbars at 39772 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Variables: Handlebars at 44384 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Variables: doT at 59409 ops/sec
Firefox 27.0.0 (Mac OS X 10.9) Variables: lodash at 38702 ops/sec
Firefox 27.0.0 (Mac OS X 10.9)
Complex: ???? at 70418 ops/sec (1.75x faster than doT)
Loops: ???? at 91616 ops/sec (2.18x faster than doT)
Markup: ???? at 60520 ops/sec (1.65x faster than HTMLbars)
Variables: ???? at 96360 ops/sec (1.62x faster than doT)
Safari 7.0.1 (Mac OS X 10.9.1) Complex: ???? at 112226 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Complex: Handlebars at 19631 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Complex: doT at 23976 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Complex: lodash at 20570 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Loops: ???? at 132643 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Loops: Handlebars at 23729 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Loops: doT at 26615 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Loops: lodash at 23219 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Markup: ???? at 118708 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Markup: HTMLbars at 76993 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Markup: Handlebars at 16625 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Markup: doT at 17254 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Markup: lodash at 16296 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Variables: ???? at 158901 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Variables: HTMLbars at 86282 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Variables: Handlebars at 28613 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Variables: doT at 32622 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1) Variables: lodash at 26922 ops/sec
Safari 7.0.1 (Mac OS X 10.9.1)
Complex: ???? at 112226 ops/sec (4.68x faster than doT)
Loops: ???? at 132643 ops/sec (4.98x faster than doT)
Markup: ???? at 118708 ops/sec (1.54x faster than HTMLbars)
Variables: ???? at 158901 ops/sec (1.84x faster than HTMLbars)
Chrome 33.0.1750 (Mac OS X 10.9.1) Complex: ???? at 73807 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Complex: Handlebars at 27460 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Complex: doT at 50807 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Complex: lodash at 36129 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Loops: ???? at 98253 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Loops: Handlebars at 46144 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Loops: doT at 54053 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Loops: lodash at 41430 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Markup: ???? at 113562 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Markup: HTMLbars at 66874 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Markup: Handlebars at 28774 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Markup: doT at 30032 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Markup: lodash at 27260 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Variables: ???? at 112530 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Variables: HTMLbars at 55258 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Variables: Handlebars at 67007 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Variables: doT at 75786 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1) Variables: lodash at 56775 ops/sec
Chrome 33.0.1750 (Mac OS X 10.9.1)
Complex: ???? at 73807 ops/sec (1.45x faster than doT)
Loops: ???? at 98253 ops/sec (1.82x faster than doT)
Markup: ???? at 113562 ops/sec (1.70x faster than HTMLbars)
Variables: ???? at 112530 ops/sec (1.48x faster than doT)

Potential names:

  • domly - like Dolly the sheep, DOMly uses cloneNode and createElement to be fast (via Aysegul Yonet). 🐑
  • dolly - like Dolly the sheep, the language clones HTML" (via Aysegul Yonet)
  • doml - dom language
  • domtl - dom template language
  • domtml - dom template markup language
  • tmul - "template mark-up language" (via Parag Tope)
    • Also stands for "The Milwaukee Urban League" and a problem code from SPOJ
  • templet - "root word of template, petite version of template" (via Parag Tope)
  • Chevron - "the name of an angle bracket and also a style of mustache" (via Tyles Briles)
  • knit - "it weaves the web"
  • elplato - "element" + "plate" = el plate = el plato en español
  • cplate - "create" + "plate"
  • snap - "Because it's fast" (via CJ Winslow)
  • switfly - "Because it's fast" (via CJ Winslow)
  • httml - "Hyper text template markup language"
  • apex - "at the top of the benchmarks" (via Kristina Garfinkel)
  • walrus - "it's like mustache, but better" (via Tyler Briles)
  • HTMLStache - "it's HTML-y mustache" (via Tyler Briles)
  • DOMinator - "templates with the DOM that dominate the rest" (via Adnan Wahab)
  • ftml - "fast template mark-up language" (via Parag Tope)
    • Also stands for "For the motherland"

Can't use:

  • tml - "template markup language" (via Bianca Gandolfo)
    • Already taken by a random programming language
  • atml - "a template markup language"
    • There's already an IEEE standard for ATML (Automated test markup language)
  • mustdash - (via Bianca Gandolfo)
    • Language is not mustachey enough.
  • httl - "hyper text template language"
    • Already taken by a Java project
  • qtml - "quick template mark-up language" (via Parag Tope)
    • Already exists as "QuickTime Media Layer"
this["templates"]["Complex"] = function anonymous(data_0) {
var frag = document.createDocumentFragment();
var data = data_0;
var el0 = document.createElement("h1");
el0.textContent = data_0["header"];
frag.appendChild(el0);
if (data_0["hasItems"]) {
var el4 = document.createElement("ul");
var iterated_1 = data_0["items"];
for (var i1 = 0, ni1 = iterated_1.length; i1 < ni1; i1++) {
var data_1 = data = iterated_1[i1];
if (data_1["current"]) {
var el10 = document.createElement("li");
var el11 = document.createElement("strong");
el11.textContent = data_1["name"];
el10.appendChild(el11);
el4.appendChild(el10);
}
else {
var el14 = document.createElement("li");
var el15 = document.createElement("a");
el15.setAttribute("href", data_1["url"]);
el15.textContent = data_1["name"];
el14.appendChild(el15);
el4.appendChild(el14);
}
}
frag.appendChild(el4);
}
else {
var el21 = document.createElement("p");
el21.textContent = "The list is empty.";
frag.appendChild(el21);
}
return frag;
};
@tywrr
Copy link

tywrr commented Feb 26, 2014

Like the name choice. Domly.

@Whoaa512
Copy link

Awesome name::bangbang:: super fast:interrobang::dash:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment