Likely.js is a templating language for HTML that offers automatic DOM diff, and bidirectional data binding.
This output is here for illustration purpose.
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();
Produce
<p>1</p><p>2</p><p>3</p>
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:
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.
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.
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.
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 keywords are minimal but sufficient to do anything.
Iterate over an object or an array. Syntax:
for [key,] value in iterable
"{{ key }} : {{ value }}"
Clasic conditions. Syntax:
if expression
"something"
elseif expression
"something else"
else
"otherwise"
To declare a string you need to start and end a line with a double quote.
p
"my paragraph text"
By using 3 double quotes strings can span across multiple lines
p
"""my long
paragraph of text"""
A line starting by a # will be ignored.
p
# some math
{{ 1 + 2 }}
Produce
<p>3</p>
Empty lines are significant and produce a carriage return inside the rendered HTML.
p p
Produce
<p></p>
<p></p>
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 }}
p class="{{ 1 == 1 && 'selected' }}"
Produce
<p class="selected"></p>
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.
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) }}
The context object represent the variables available in the current scope. It is directly available as an argument.
a lk-click={{ callMe(context) }}
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.