<section id="introduction" data-bind="project">
<h1 data-bind="name"></h1>
<hr>
<p class="description" data-bind></p>
<dl data-bind="guidelines">
<dt data-bind="title">
<dd data-bind="description">
</dl>
</section>
<script src="databinder.js"></script>
<script>
databind("#introduction").set({
project : {
name: "Databinder",
description: "Databinder is a client-side JavaScript DOM-based templating system...",
guidelines: [ {
title: "Intuitive, declarative, low logic templating",
description: "Automatic bindings based on the HTML element..."
},{
title: "Manual and targeted view updates",
description: "Two way data binding, as implemented by..."
},{
title: "No String interpolation to preserve the link between model and view",
description: "Each data representation deserves its own HTML..."
} ]
}
});
</script>
Quick answer: dynamically generated content. Generating pages with custom data inside can be done with string interpolation, but when dealing with more complex data structures (loops, conditional formatting, advanced data transformations), it requires a more powerful solution. Template engines have enough functionnalites to adress most of these issues. Templates are easy to read, easy to maintain, and can be modified to some extent by other people than developers.
Since 1995 and Web 2.0 stuff, many web developers are doing templating on the server side thanks to technologies like PHP or JSP. Then AJAX came, and Single Page Applications gained popularity. Loading just one HTML page (often static) from the server and rely on AJAX requests for navigation and interaction tends to lighten HTTP requests and reduce page load time. It also helps websites to feel more dynamic. The next step was to update the website views without actually requesting the server, for actions that do not need it, for offline usages or for latency compensation. All these innovations have for first consequence to move more and more application logic to the client-side. The developers then had to adapt and find new tools to handle DOM manipulation and interact with the page in JavaScript more efficiently.
You probably all know jQuery, the most popular JavaScript library that is well known for its omnipotent selector function. What it lacks is a way to generate large parts of HTML with associated data. That's why John Resig, the creator of the jQuery library, published its own micro-templating engine. Client-side templating is widely used today and an essential part of many modern web application frameworks.
The DOM is the hierarchical and semantic representation of the web page content. It makes sense to use it as a basis for a templating engine. String-based templating provide more flexibility, but in my opinion, this gain in flexibility is most often used for wrong reasons and increases the gap between the data model and HTML. More concerns are explained in this article : String Templating considered harmful
I used a bunch of different templating libraries for professional and personal projets. I wrote this article (in French) outlining the pros and cons of each one in my opinion. Since none satisfied me fully, I decided to write my own library. I wanted a DOM-based low-logic templating with a clear declarative syntax like Knockout, but lightweight and not part of a bigger solution (I am not a big fan of all-in-one frameworks). I found the result convincing enough to share it with you.
Databinder is only 14 KB (7.5KB minified, even less when gzipped). All the examples on this page have been passed through Databinder. Did you notice ? It should be fast enough so that you do not see anything. Actually, it took milliseconds.
Databinder is efficient enough to be used on low-end devices or for small animations. Also, one of the strengths of DataBinder is that you choose exactly which part of the DOM to update and when.
Databinder is unit tested and supported on Internet Explorer 9 and above, and latest versions of Chrome, Firefox and some mobile browsers like Android Stock Browser, Chrome Mobile, Firefox Mobile, Opera Mobile... Actually, it should work on any decent web browser published since IE9.
Databinder does not need any dependencies. However, you can easily plug it with other libraries. For example, a jQuery adapter is available to download so you can call Databinder that way : $("#myElement").databind(data);
get methodYou can report bugs and contribute to the project on GitHub Databinder repository.
<a data-bind="text: label, href: url, title: tooltip"></a>
var thatLinkElement = document.querySelector("a");
databind(thatLinkElement).set({
label: "My website",
url: "http://syllab.fr",
tooltip: "syllab.fr"
});
The data-bind HTML attribute specifies all the data bindings between your model and the current HTML element. The value should be a comma separated list of attribute:value pairs. If no match is found in your data model, or if value matched is undefined or null, no binding is done and the element is kept unchanged.
The text binding attribute replaces inner tag content with the matching object in current scope
evaluated as a HTML-escaped String. If you want to render unescaped HTML, use the html attribute.
The href and title binding attributes refer to the related HTML attributes. All
binding attributes that do not refer to a binding keyword in this list
are assigned as HTML attributes.
<h3>Tic Tac Toe</h3>
<table>
<tbody data-bind="loop: { in: grid, as: row }">
<tr data-bind="loop: { in: row, as: cell }">
<td data-bind="cell">
</tr>
</tbody>
</table>
databind("table").set({
grid: [ ["X", " ", "O" ],
["O", "X", " " ],
[" ", " ", "X" ] ]
});
The loop attribute is used to loop over an array and repeat the inner HTML content for each array element. Each loop iteration is associated to the current array element as a new scope. In the inner scopes, you can refer to the current index and value with loopIndex and loopValue variables. To improve readability, you can rename these variables by specifying as attribute for the current value and at attribute for the current index.
If you want to remove the element if the array passed is empty, add an if binding : if: array.length
<div id="palette">
<ul data-bind="loop: { in: palette, at: name, as: hex }">
<li data-bind="text: name, style: { color: hex }"></li>
</ul>
<ol data-bind="loop: { in: favorites, at: num, as: color }">
<li data-bind="style: { backgroundColor: getColorValue }">
Favorite <b data-bind="num"></b>
</li>
</ol>
</div>
databind("#palette").set({
palette: {
"Light Blue": "#ADD8E6",
"Chocolate": "#D2691E",
"Olive": "#808000",
"Salmon": "#FA8072",
"Indigo": "#4B0082"
},
favorites: [ "Chocolate", "Olive", "Salmon" ],
getColorValue: function(data){
return data.palette[this.color];
}
});
The loop binding also lets you iterate over all the enumerable properties of an object: in object, at keys, as values.
<div id="feedback">
<span data-bind="if: showSecret">I should not be here...</span>
<input id="check" type="checkbox" data-bind="checked: understood"/>
<label for="check">Okay I got it</label>
<p>Is it hard so far ?</p>
<select data-bind="loop: { in: answers, at: i }">
<option data-bind="value: i, text: text, selected: default"></option>
</select>
</div>
databind("#feedback").set({
understood: true,
showSecret: false,
answers: [
{ text: "Easy as pie" },
{ text: "It's all right", default: true },
{ text: "I need coffee !" }
]
});
Is it hard so far ?
Some HTML attributes such as checked, selected or disabled are expecting Boolean values - browsers treat them as true if they are present, regardless of the value. There is also some special binding attributes expecting booleans. For example, if will remove the element from the generated HTML if false, whereas visible will apply style="display:none;" if false.
Other attribute bindings are ignored and the element is kept unchanged when the value passed to the binding is undefined or null. However, when the value matched is a Boolean, the attribute is set without any value if true and removed if false.
<h3>Prime Numbers In Fibonacci Suite</h3>
<p data-bind="loop: { in: suiteGenerator, as: number }">
<span data-bind="text: number, class: { prime: isPrime }">
</span> ;
</p>
<style>
span.prime {
font-weight: bold;
text-decoration: underline;
}
</style>
databind("body").set({
suite: [],
suiteGenerator: function(scope){
var n = scope.suite.length;
scope.suite[n] = n<2 ? 1 : scope.suite[n-2] + scope.suite[n-1];
return scope.suite[n] < 100 ? scope.suite[n] : null;
},
isPrime: function(){
for(var n=2; n<= ~~(this.number/2); n++){
if ( this.number % n === 0 ){
return false;
}
}
return true;
}
});
;
When a Function is bound, it is executed in the context of the current scope and its result is used for the data-binding instruction. Inside the function, this refers to the current scope. The current scope of a node corresponds to the data attached to the closest data-bound parent node. In this example, the current scope of the span tag is the data bound to the p tag, ie suiteGenerator with 'number' variable declared as iterator.
The arguments passed to binding functions are :
When a Function is used within a loop binding, it acts as a generator which iterate on return values until this function returns null. Using functions is very powerful and can help you implement complex things like generators or recursion. If you do not figure how to write your template, a function will probably help you to come through.
<p class="to-update">You have been
<span data-bind="text: timePassed"></span> seconds on this page.
</p>
<p>Initially this value was
<span data-bind="text: timePassed"></span>.
</p>
var dataReference = { timePassed: 0 };
databind("body").set(dataReference);
setInterval(function(){
dataReference.timePassed++;
databind("p.to-update").reset();
}, 1000);
You have been seconds on this page.
Initially this value was :
reset must be called after a first call to set function. Its purpose is to update bindings values for a specified element and its child nodes. You must have kept a reference to these values so that the binding is maintained when you reassign your model variables ; it means that you can not use primitive values (boolean, number, string) as binding reference, otherwise this reference will be lost when assigning a new value. Remember to manipulate objects rather than primitives to benefit from the automatic binding update, as shown in the example.
One advantage of Databinder compared to other String-based templating solutions is that you can easily update a subpart of a template without affecting the rest. It is also possible to update bindings from several distinct calls of set in one call of reset on a parent element.
<div id="mylist">
<h3>Dynamic list (currently
<span data-bind="itemCollection.length"></span>
items)</h3>
<ul data-bind="loop: itemCollection">
<li>
Item <span data-bind="num"></span>
- <a data-bind="click: remove">remove</a>
</li>
</ul>
<input type="button" value="Add another item"
data-bind="click: addItem" />
</div>
var List = function(selector){
this.databinding = databind(selector);
this.itemCounter = 0;
this.itemCollection = [];
this.databinding.set(this);
};
List.prototype = {
addItem: function(){
this.itemCollection.push({
num: ++this.itemCounter
});
this.databinding.reset();
},
remove: function(event, list){
event.preventDefault();
list.itemCollection.splice(this.loopIndex, 1);
list.databinding.reset();
}
};
var myList = new List("#mylist");
myList.addItem();
myList.addItem();
You already know how to listen to events on elements with on[event] attributes, such as onclick. But in the callback function, you need to manually retrieve the object in your model associated to this element. The other option is to bind listeners from your Javascript controllers, using addEventListener for example. But when elements are generated from a templating system, you have to either use delegated events or redo all the event-bindings after rendering. It is quite annoying, so event bindings attributes have been added in order to quickly create listeners from your templates just after elements have been created.
All the DOM events are supported. Some may say that it breaks the view/controller separation, so use it or not. Anyway, it can sometimes make things easier.
In callbacks functions, this refers to the current scope just like data-binding functions. The arguments passed to callbacks are :
<div id="scoreboard">
<h2>Feuille des scores</h2>
<form data-bind="submit: save">
<dl data-bind="loop: scores">
<dt><input type="text" data-bind="name"></dt>
<dd><input type="number" data-bind="score"></dd>
</dl>
<input type="submit" value="Enregistrer">
</form>
<p data-bind="winner">
The winner is <span data-bind="name"></span> with <span data-bind="score"></span> points.
</p>
</div>
databind("#scoreboard".set({
scores: [
{ name: "Joe", score: 6500 },
{ name: "Jack", score: 8200 },
{ name: "Jim", score: 5750 }
],
winner: function(){
return this.scores.reduce(function(a,b){
return a.score > b.score ? a : b;
});
},
save: function(event){
event.preventDefault();
databind("#scoreboard form").get();
databind("#scoreboard p").reset();
}
});
The winner is with points.
get function does the opposite of the reset function. It must be used after a first call at set too, but instead of updating the document under the model changes, it updates the model according to the changes in the document. Currently, this applies only to value bindings of <input> elements and <textarea> contents. This method is very useful for editing forms where you can send new data in one direction or another.
<section id="flags">
<h1>Some flags by continent </h1>
<h2>Europe</h2>
<ul data-bind="europe">
<li data-bind="template: flag"></li>
</ul>
<h2>Asia</h2>
<ul data-bind="asia">
<li data-bind="template: flag"></li>
</ul>
<h2>Africa</h2>
<ul data-bind="africa">
<li data-bind="template: flag"></li>
</ul>
</section>
<template id="flag">
<img data-bind="src: img, alt: country">
<h3 data-bind="country"></h3>
<dl>
<dt>Capital: </dt>
<dd data-bind="capital"></dd>
</dl>
</template>
databind('#flags').set({
africa: [
{ country:"Senegal", capital:"Dakar", img:"sn.png" },
{ country:"Namibia", capital:"Windhoek", img:"na.png" },
{ country:"Egypt", capital:"Cairo", img:"eg.png" }
],
asia: [
{ country:"Russia", capital:"Moscow", img:"ru.png" },
{ country:"Israel", capital:"Jerusalem", img:"il.png" },
{ country:"Japan", capital:"Tokyo", img:"jp.png" }
],
europe: [
{ country:"France", capital:"Paris", img:"fr.png" },
{ country:"Sweden", capital:"Stockholm", img:"se.png" },
{ country:"Germany", capital:"Berlin", img:"de.png" }
]
});
The template bindings allow you to reuse HTML code fragments with optionally other bindings, instead of having redundant code. This helps to make your HTML cleaner and easier to maintain. This binding expects as value a template name corresponding to a <template> element id attribute in the document. The template HTML content will be copied into the current element and the binding process will continue in the same scope.
The <template> element has to be outside any data-bound element. Templates can be encapsulated and modified between two databind calls.
If a binding attribute is not in this list and is not an event attribute, it will be set as an attribute of the current HTML element.
Object, Array, String or binding set
add or remove specified classes to the current element
<input data-bind="class: properties" />
<button data-bind="class: classNames">Click me</button>
<section class="old classes" data-bind="class: otherClasses"></section>
<p class="stuff" data-bind="text: message, class: { important: isImportant, business: isBusiness }"></p>
databind('input').set({
properties: {
blue: false,
red: true
}
});
databind('button').set({
classNames: "some classes as string"
});
databind('section').set({
otherClasses: ["some", "other", "classes"]
});
databind('p.stuff').set({
message: "This is important stuff dude !",
isImportant: true,
isBusiness: false
});
<input class="red" />
<button class="some classes as string">Click me</button>
<section class="some other classes"></section>
<p class="stuff important">
This is important stuff dude !
</p>
Boolean
hide the current element by applying style="display:none;" if true
<ol data-bind="loop: items">
<li data-bind="text: loopValue, hidden: autocensor"></li>
</ol>
databind('ol').set({
items: ["Some", "items", "seem", "to", "have", "disappeared"],
autocensor: function(){
return this.loopIndex % 2 === 0;
}
});
String or HTMLElement
set the inner HTML content of the current element. Beware of XSS attacks ! If the inserted content does not come from a trusted source, you should rather use the text binding.
<p data-bind="html: message">stuff lost after templating</p>
databind('p').set({
message: "I needed <strong style='color:blue;'>unescaped HTML content</strong> here."
});
stuff lost after templating
Boolean
delete the current element and stop processing of its child nodes if false
<div>
<p data-bind="if: results|none">No results found<p>
<ul data-bind="if: results|some, loop: results">
<li data-bind="loopValue"></li>
</ul>
</div>
databind('div').set({
results: []
});
No results found
<div>
<p>No results found<p>
</div>
Boolean
same as a negated if binding ; delete the current element if true
Array or Object
repeats the inner content of the current element for each array element. Each iteration creates a new scope with loopValue and loopIndex variables which can be renamed by setting as or at attributes in the loop binding set.
See example code for Array
See example code for Object
Object, String or binding set
add or remove inline CSS styles to the current element
<input style="background-color: lime;" data-bind="style: otherRules" />
<button data-bind="style: ruleSet">Click me</button>
<p data-bind="text: message, style: { fontSize: size, color: color }"></p>
databind('input').set({
otherRules: {
borderWidth: "10px",
borderColor: "red"
}
});
databind('button').set({
ruleSet: "text-transform: uppercase; font-weight: bold;"
});
databind('p').set({
message: "Big red stuff !",
color: "rgb(255,0,0)",
size: "3em"
});
String
set the text content of the current element, escaping HTML and replacing any existing content
<p data-bind="text: message">stuff lost after templating</p>
databind('p').set({
message: "Don't worry about <iframe>XSS</iframe>"
});
stuff lost after templating
template name
import a <template> content into the current element and continues the datadinding in the same scope
Example code
Boolean
same as a negated hidden ; hide the current element by applying style="display:none;" if false
<ol data-bind="loop: items">
<li data-bind="text: loopValue, visible: autocensor"></li>
</ol>
databind('ol').set({
items: ["Some", "items", "seem", "to", "have", "disappeared"],
autocensor: function(){
return this.loopIndex % 2 === 0;
}
});
Object
creates a new scope based on the object passed
My name is , my son is called and my grand-son is
My name is , my son is called and my grand-son is
My name is , my son is called and my grand-son is
My name is , my son is called and my grand-son is
databind(document.body).set({
name: "Karl",
child : {
name: "Karl Junior",
child: {
name: "Baby Karl"
}
}
});
My name is , my son is called and my grand-son is
My name is , my son is called and my grand-son is
My name is , my son is called and my grand-son is
My name is , my son is called and my grand-son is
Data scope resolution works similarly to closures in JavaScript: when a value is undefined in the current scope, Databinder will traverse the scope chain all the way up until it finds a matching value. However, when you specify a value beginning with one or more points, you explicitly specify what scope watch.
When the attribute is omitted in a binding declaration, a default one is bound depending on the element and the matched data type.
| Tag | Data type | Attribute bound by default |
|---|---|---|
| all |
Object
|
with
|
| all |
Array
|
loop
|
| all |
Element
|
html
|
<input type="checkbox">,
<input type="radio">
|
Boolean
|
checked
|
<option>
|
Boolean
|
selected
|
| other tags |
Boolean
|
if
|
<audio>,
<embed>,
<img>,
<iframe>,
<script>,
<source>,
<track>,
<video>
|
String
|
src
|
Other <input> types
|
String / Number
|
value
|
| other tags |
String / Number
|
text
|
When the data-bind attribute is specified without any value, Databinder will try to guess what value you are referring to by checking the id, name and class attributes of the element in this order. Then, the default attribute is bound depending on the type of the value matched.
<ul id="people" data-bind>
<li class="item name" data-bind>
</ul>
<script>
databind("body").set({
people: [ { name : "Jim" },
{ name : "Jeff" },
{ name : "Joe" } ]
});
</script>
ul has ID #peoplepeople is defined in data as Arrayul to loop: peopleli has no ID or nameli has class .item but item is undefined in current data scopeli has class .name and name is defined as String li to text: nameExtensions are helper functions called on values matched after the data binding. They are useful for adding basic logic instructions, better formatting and sorting/selecting options to your templates. You can apply a extension to a value by writing |extensionName behind the name of the value in the binding.
Extensions can be chained, so you can write :
data-bind="text: sentence | trim | capitalize"
Also, they can take number and words as parameters, separated by whitespaces after the extension name :
data-bind="if: sum | between 5 10"
data-bind="loop: messages | filter byDate"
You can add your own extensions very easily. For example, let's say you want to use the super date formatting library Moment.js. Declare your extension this way:
databind.extensions.calendar = function(){
return moment(+this).calendar();
};
<div class="post">
<span class="name" data-bind></span>
<span class="date" data-bind="date|calendar"></span>
<p data-bind="message"></p>
</div>
databind(".post").set({
name: "Author",
date: Date.now(),
message: "Hello databinder !"
});
There are no extensions bundled with DataBinder source file. A list of extensions is proposed to you below, so that you can pick just the ones you want and learn how to add your own extensions. Just copy the extension code at the end of the source file DataBinder or in a separate script, at your convenience.
true if value == param
databind.extensions.equals = function(x){ return this == x; };
true if value > Number (or value.length for Arrays)
databind.extensions.moreThan = function(n){
return (Array.isArray(this) ? this.length : +this) > n;
};
true if value >= start && value <= end (or value.length for Arrays)
databind.extensions.between = function(start, end){
var n = (Array.isArray(this) ? this.length : +this);
return n >= start && n <= end;
};
true if every array elements have a truthy property p or returned true through function f
databind.extensions.every = function(f){
return Array.isArray(this) && this.every(typeof f == "function" ? f : function(){ return this[f] });
};
true if some of the array elements have a truthy property p or returned true through function f
databind.extensions.some = function(f){
return Array.isArray(this) && this.some(typeof f == "function" ? f : function(){ return this[f] });
};
true if null, undefined, empty array or if none of the array elements passed to the parameter function returned true
databind.extensions.none = function(fn){
return this === null || this === undefined || this.length === 0
|| (fn !== undefined && Array.isArray(this) && !this.some(fn));
};
array.sort(f);
databind.extensions.sort = function(f){
return Array.isArray(this) ? this.sort(f) : [];
};
array.filter(f);
databind.extensions.filter = function(f){
return Array.isArray(this) ? this.filter(f) : [];
};
databind.extensions.date = function(){ return new Date(this).toLocaleDateString(); };
databind.extensions.time = function(){ return new Date(this).toLocaleTimeString(); };
Math.round(value) with an optional number of decimals to keep.
databind.extensions.round = function(n){
var f = Math.pow(10, n|0);
return Math.round( f * (+this) ) / f;
};
databind.extensions.trim = String.prototype.trim;
databind.extensions.lowercase = String.prototype.toLowerCase;
databind.extensions.uppercase = String.prototype.toUpperCase;
databind.extensions.capitalize = function(){
return String(this).charAt(0).toUpperCase() + String(this).slice(1);
};
<div id="clock">
<div class="needle hour" data-bind="style: hour"></div>
<div class="needle minute" data-bind="style: minute"></div>
<div class="needle second" data-bind="style: second"></div>
<span data-bind="text: date|time"></span>
</div>
var clock = {
elm: "#clock",
date: new Date(),
tranform: function(percentage){
var angle = ~~(percentage * 360 - 90);
return "transform: rotate(" + angle + "deg);"
+"-webkit-transform: rotate("+ angle +"deg);"
},
second: function(){ return this.tranform(this.date.getSeconds() / 60); },
minute: function(){ return this.tranform(this.date.getMinutes() / 60); },
hour: function(){ return this.tranform( (this.date.getHours() % 12 + this.date.getMinutes() / 60) / 12); },
init: function(){
this.databinding = databind(this.elm).set(this);
setInterval(this.update.bind(this), 1000);
},
update: function(){
this.date = new Date();
this.databinding.reset();
}
};
clock.init();
Templates can refer to themselves.
<div id="treeview">
<div class="root folder" data-bind="template: tree-item">
Loading tree view...
</div>
</div>
<template name="tree-item">
<span class="icon" data-bind="click: open, class: { opened: isOpened }"></span>
<span data-bind="text: name"></span>
<ul data-bind="if: isOpened, loop: items">
<li class="folder" data-bind="if: isFolder, template: tree-item"></li>
<li class="file" data-bind="ifnot: isFolder">
<span data-bind="name"></span>
</li>
</ul>
</template>
databind("#treeview").set({
name: "Documents",
isOpened: true,
items: [{
name: "Images",
isOpened: false,
items: [{
name: "Holidays 2014",
isOpened: false,
items: [{ name: "photo1.jpg" },
{ name: "photo2.jpg" }]
},{name: "background.png" }]
},{
name: "Music",
isOpened: false,
items: [{ name: "Hotel California.mp3" },
{ name: "Wasted time.mp3" }]
}],
isFolder: function(){ return "items" in this; },
open: function(event, scope, elm){
this.isOpened = !this.isOpened;
databind(elm.parentNode).reset();
}
});
Internationalization involves many issues that are adressed by many existing solutions on the server or client side. Some of these issues relate directly to templating, like currency or date formatting. You can declare a few extensions to help you connect templates to internationalization APIs.
(function(Intl){
databind.lang = navigator.language;
databind.dateTimeFormats = {
"short": {year: "2-digit", month: "2-digit", day:"2-digit"},
"long": {weekday: "long", year: "numeric", month: "long", day: "numeric"},
"hour": {hour: "numeric", minute: "numeric", second: "numeric"},
"full": {year: "numeric", month: "long", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric"}
// define your own formats here
};
databind.extensions["intl-currency"] = function(currency){
if(Intl && Intl.NumberFormat){
return new Intl.NumberFormat(databind.lang, { style: "currency", currency: currency }).format(this);
}
return this;
};
databind.extensions["intl-date"] = function(format){
if(Intl && Intl.DateTimeFormat){
return new Intl.DateTimeFormat(databind.lang, databind.dateTimeFormats[format]).format(this);
}
return this;
};
})(window.Intl);
<h2>Your account activity</h2>
<time data-bind="reportDate | intl-date 'long'"></time>
<table>
<thead>
<th>Date</th>
<th>Description</th>
<th>Amount</th>
</thead>
<tbody data-bind="transactions">
<tr>
<td data-bind="date | intl-date 'full'"></td>
<td data-bind="description"></td>
<td data-bind="amount | intl-currency 'EUR'"></td>
</tr>
</tbody>
</table>
<label>Override default language</label>
<select data-bind="change: onLangChange, loop: langs">
<option data-bind="value: tag, text: label, selected: isCurrentLang"></option>
</select>
databind("#intl-demo").set({
reportDate: new Date("2014-05-27T08:00:00"),
transactions: [
{ date: new Date("2014-05-26T13:12:27")
, description: "Restaurant"
, amount: 37.50 },
{ date: new Date("2014-05-26T16:30:12")
, description: "Bowling"
, amount: 18.00 },
{ date: new Date("2014-05-25T21:17:51")
, description: "Gas invoice"
, amount: 62.27 } ],
langs: [ { tag: "en", label: "English" },
{ tag: "fr", label: "French" },
{ tag: "de", label: "Deutsch" },
{ tag: "es", label: "Spanish" } ],
isCurrentLang: function(){ return this.tag === databind.lang; },
onLangChange: function(event, scope, elm){
databind.lang = elm.value;
databind"#intl-demo").reset();
}
});
| Date | Description | Amount |
|---|---|---|
The classic todo-app example as shown on todomvc.com
<div id="test-zone">
</div>
databind("#test-zone").set({
});