Menu French


<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> 

Why should I need templating ?

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.

Why do it client-side ?

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.

Why use a DOM-based templating and not a String-based much more common ?

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

Why another templating library ?

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.

Is it performant ?

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.

What browsers are supported ?

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.

Can I use it with my favorite framework / library ?

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);


Download

Current version : v0.7

Changelog
v0.7
added support for litteral strings as parameters
v0.6
new functional-oriented API
added get method
v0.5
externalised bundled extensions
added Array and Boolean extensions
v0.4
added extensions
changed template functions arguments
v0.3
added templates & events
v0.2
new syntax for loops
automatic binding table revised

GitHub repository

You can report bugs and contribute to the project on GitHub Databinder repository.


Binding content and attributes

HTML

<a data-bind="text: label, href: url, title: tooltip"></a>
            
JavaScript

var thatLinkElement = document.querySelector("a");
databind(thatLinkElement).set({
	label: "My website",
	url: "http://syllab.fr",
	tooltip: "syllab.fr"
});
Result

<a href="http://syllab.fr" title="syllab.fr">My website</a>

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.


Loops over Arrays

HTML

<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>
JavaScript

databind("table").set({
	grid: [ ["X", " ", "O" ],
	        ["O", "X", " " ],
	        [" ", " ", "X" ] ]
});
Result

Tic Tac Toe

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


Traversing properties and indexes

HTML

<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>
JavaScript

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];
	}
});
Result
  1. Favorite

The loop binding also lets you iterate over all the enumerable properties of an object: in object, at keys, as values.


Conditionals with Booleans

HTML

<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>
JavaScript

databind("#feedback").set({
	understood: true,
	showSecret: false,
	answers: [
		{ text: "Easy as pie" },
		{ text: "It's all right", default: true },
		{ text: "I need coffee !" }
	]
});
Result
I should not be here...

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.


Playing with Functions

HTML

<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>
JavaScript

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;
	}
});
Result

Prime Numbers In Fibonacci Suite

;

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 :

  1. the root scope (the one you pass through databind)
  2. the current HTML element.

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.


Partial or complete view updates

HTML

<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>
JavaScript

var dataReference = { timePassed: 0 };
databind("body").set(dataReference);
setInterval(function(){
	dataReference.timePassed++;
	databind("p.to-update").reset();
}, 1000);
Result

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.


Listening to Events

HTML

<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>
            
JavaScript

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();
Result

Dynamic list (currently items)

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 :

  1. the event object
  2. the root scope (the one you pass through databind)
  3. the current HTML element

Saving data from form fields

HTML

<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>
JavaScript

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();
   }
});
Result

Scoreboard

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.


Using templates for code reuse

HTML

<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>
            
JavaScript

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" }
	]
});
            
Result

Some flags by continent

Europe

Asia

Africa

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.


Special binding attributes list

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.


Omitting attribute : Default Attribute Binding

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

Omitting both attribute and value : automatic binding

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.

HTML & JavaScript

<ul id="people" data-bind>
	<li class="item name" data-bind>
</ul>

<script>
	databind("body").set({
		people: [ { name : "Jim"  },
					 { name : "Jeff" },
					 { name : "Joe"  } ]
	});
</script>
Automatic binding
  1. ul has ID #people
  2. people is defined in data as Array
  3. bind ul to loop: people
  1. li has no ID or name
  2. li has class .item but item is undefined in current data scope
  3. li has class .name and name is defined as String
  4. bind each li to text: name

Declare extensions to extend template syntax

Extensions 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();
};
HTML

<div class="post">
<span class="name" data-bind></span>
<span class="date" data-bind="date|calendar"></span>
<p data-bind="message"></p>
</div>
JavaScript

databind(".post").set({
	name: "Author",
	date: Date.now(),
	message: "Hello databinder !"
});
Result


Some extensions as examples

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.


Advanced examples

Interval updates

HTML

<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>
         
JavaScript

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();
Result

Recursive templating

Templates can refer to themselves.

HTML

<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>
         
JavaScript

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();
	}
});
Result
Loading tree view...

Internationalization helpers

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);
HTML

<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>
JavaScript

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();
   }
});
Result

Your account activity

Date Description Amount

TodoMVC App

The classic todo-app example as shown on todomvc.com


Try by yourself !

HTML
<div id="test-zone">
</div>
JavaScript
databind("#test-zone").set({
});
Result