Feature highlights

  1. A simple template language to generate HTML.
  2. Bidirectional data binding to any JavaScript object.
  3. A template object renders it's result in a render tree. Using 2 of those tree likely.js produce a DOM manipulation diff that updates the existing DOM with minimal impact.
  4. This library works and is tested on Chrome, Firefox and IE11.
  5. Test coverage is bigger than 90%.

Template Syntax Example

Data used to render the Template


Result

Html Output

This output is here for illustration purpose.



Other examples

  1. TodoMVC example.
  2. A basic CRUD application demo.
  3. Bidirectional data binding with HTML form elements.
  4. Recursive structure using Component.
  5. Nested components with context watching.

Documentation

Table of content

Basic complete example

var data = {list:[1, 2, 3]};
var template = likely.Template([
'for key, value in list',
'  p',
'    {{ value }}'
]);

var div = document.createElement("div");
document.body.appendChild(div);
var binding = new likely.Binding(div, template, data);
binding.init();

View this example on RequireBin

Produce

<p>1</p><p>2</p><p>3</p>

Generating HTML with the template language

Any line starting by an alphabetical letter which is not part of the language keywords will produce an HTML node. Indentation indicates the nesting of the nodes. Only one HTML node by line is allowed.

ul
  li
    a href="/"
      "Home"

  li
    a href="/products"
      "Products"

Will produce

<ul><li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li></ul>

The language keywords that cannot be used as HTML nodes are:

How does Likely.js modify the DOM

To modify the DOM Likely.js use a list of differences between 2 arbitrary abstract render tree.

A simple heuristic algorithm is used that aims to minimize the amount of changes needed. To acomplish this goal the algorithm is only allowed to add and remove elements, change attributes, and modify the content of text nodes.

Finally the diff produced by the algorithm is applied to the DOM to bring it up to a state consitent with the data.

View an example on RequireBin

Bidirectional Data Binding

Likely.js regonize elements with a value attribute (input, textarea and select) and will produce specific HTML that enable to update automaticaly the data object. The condition for the data binding to work is to set the value attribute to a simple name expression that exist within the data.

var tpl = likely.Template("input value={{ name }}");
var div = document.getElementById('someDiv');
var data = {name:"Chevalier d'Eon"};
var binding = new likely.Binding(div, tpl, data);
binding.init();

Will produce something like this

<input value="Chevalier d'Eon" lk-bind="name" lk-path=".0">

The binding object takes care of the events and reflect the changes of the input value back to the data object. The binding does so using the path to fetch the corresponding abstract render node. Using this node context the value is then changed. Finally a new tree render is triggered and the diff is applied to the DOM to reflect the data changes.

Changing the data externally

Likely.js doesn't use any dirty checking or other object proxy technics. It's up to you to know when you need to update the view after changing the data.

To do so call call the update method on the binding object.

binding.update();

This method will generate a new render tree, trigger a diff and apply those changes to the DOM.

Manual Data Binding

When the value of an HTML element has to be binded to another data target you can defined the lk-bind attribute yourself. E.g.

for n in numbers
  input type="radio" value="{{ n }}" lk-bind="selected"

There is a demo of bidirectional data binding with HTML form elements.

Flow Control

Flow control keywords are minimal but sufficient to do anything.

For Loop

Iterate over an object or an array. Syntax:

for [key,] value in iterable
  "{{ key }} : {{ value }}"

If / Elseif / Else

Clasic conditions. Syntax:

if expression
  "something"
elseif expression
  "something else"
else
  "otherwise"

Strings

To declare a string you need to start and end a line with a double quote.

p
  "my paragraph text"

Multi Line Strings

By using 3 double quotes strings can span across multiple lines

p
  """my long
     paragraph of text"""

Comments

A line starting by a # will be ignored.

p
  # some math
 {{ 1 + 2 }}

Produce

<p>3</p>

Empty lines

Empty lines are significant and produce a carriage return inside the rendered HTML.

p

p

Produce

<p></p>
<p></p>

Expressions

An expression in likely.js is just a plain and simple JavaScript function.

They are used in if/elseif nodes, HTML attributes, inside strings, or as a text node.

h1
  # expression as text node
  {{ value }}

if value > 10
  # expression inside a string
  "{{ value }} is bigger than 10"
else
  # expression inside an HTML element's  attribute
  input value={{ value }}

Conditional attributes

p class="{{ 1 == 1 && 'selected' }}"

Produce

<p class="selected"></p>

Function Call and Events

Given a function in your data

var data = {
  callMe:function(param){
    return param;
  }
};

This function callMe can be called anywhere in your template. A function call is just another expression so it will be evaluated once at the template evalutation time.

a class={{ callMe("myClass") }}

In the other hand when the function is called by an event attribute, the function is evaluated at runtime when the event is triggered.

a lk-click={{ callMe("clicked") }}

For an example of function usage checkout the basic CRUD application demo.

Passing the event object

The event object is injected into the context under the name event so it can be passed as an argument.

a lk-click={{ callMe(event) }}

Passing the context object

The context object represent the variables available in the current scope. It is directly available as an argument.

a lk-click={{ callMe(context) }}

Components

A component is a piece of reusable template that can be referenced by name within the template language. A component can also define a controller function that can manipulate the context before rendering.

var componentTemplate = [
'ul',
'  for link in links',
'    li',
'      a href={{ link }}',
'        {{ link }}'
];

var listOfLinks = likely.Component("ListOfLinks", componentTemplate, function(context) {
  // This function is executed at every tree rendering.
  // Here we enforce the maximum value of maxLinks to 5.
  var maxLinks = parseInt(context.get('maxLinks'), 10);
  var list = context.get('list');
  if(maxLinks < list.length) {
    list = list.slice(0); // copy
    list.splice(maxLinks-1);
    context.set('list', list);
  }
});

Once this component declared you can use it in your template by calling.

component ListOfLinks links={{ menu.links }} maxLinks="10"

Components can have any number of attributes. Those are evaluated at render time within current context and then added to the component's context. For this data object:

var data = {menu:{links:['home', 'products']}};

The result will be

<ul>
<li href="home">home</li>
<li href="products">products</li>
</ul>

For an more complex example of component usage check out this example of a DateRange component composed of 2 Date component.