Menu English


<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 est une bibliothèque JavaScript de templating côté client...",
		guidelines: [ {
            title: "Des templates intuitifs, déclaratifs et à faible logique",
            description: "Des liasons automatiques sont faites..."
		},{
			title: "Rafraîchissement manuel et ciblé des vues ",
			description: "La liaison de données à double-sens telle qu'implémentée par..."
		},{
            title: "Pas d'interpolation de texte pour préserver le lien entre modèles et vues",
            description: "Chaque représentation de donnée mérite son propre élément..."
		} ]
	}
});
</script> 

Pourquoi aurais-je besoin de templates ?

Réponse rapide : pour le contenu généré dynamiquement. Générer des pages web avec des données variables à l'intérieur peut se faire simplement avec de la concaténation de texte ; mais lorsque l'on travaille avec des structures de données plus complexes (boucles, formattage conditionnel, transformation de données avancé), il faut une solution plus puissante. Les moteurs de templating ont assez de fonctionnalités pour adresser la majorité de ces problèmes. Les templates sont faciles à lire, faciles à maintenir, et peuvent dans une certaine mesure être modifiés par des tiers non développeurs.

Pourquoi côté client ?

Depuis 1995 et les débuts du "Web 2.0", de nombreux développeurs web font du templating côté serveur grâce à des technologies comme PHP ou JSP. Puis AJAX est arrivé, et les web-apps monopage ont commencé à gagner en popularité. Charger une seule page HTML (très souvent statique) puis se reposer sur les requêtes AJAX pour la navigation et les interactions tend à globalement réduire le poids et la fréquence des requêtes HTTP ainsi que le temps de chargement de vos pages. Cela aide également à donner un air plus dynamique à vos pages web. La prochaine étape était logiquement d'actualiser les pages Web sans requêter le serveur, pour les actions qui peuvent s'en passer, pour un usage déconnecté ou encore pour de la compensation de latence. Toutes ces innovations ont comme première conséquence de déplacer de plus en plus de logique applicative côté client. Les développeurs doivent alors s'adapter et trouver de meilleurs outils pour manipuler le DOM et interagir avec la page en JavaScript plus efficacement.

Vous connaissez probablement tous jQuery, la bibliothèque JavaScript la plus populaire et reconnue pour sa fonction sélecteur omnipotente. Ce qui lui manque est un moyen de générer de larges pans de HTML contenant des données. C'est pourquoi John Resig, le créateur de jQuery, a publié son propre moteur de micro-templating. Aujourd'hui, les templates côté client sont largement utilisés et sont une partie essentielle de nombreux frameworks web modernes.

Pourquoi basé sur le DOM et non sur de la concaténation de texte comme la plupart ?

Le DOM est la représentation hiérarchique et sémantique du contenu d'une page web. Cela a du sens de s'en servir comme base pour un moteur de templating. Les templates basés sur les String offrent davantage de flexibilité, mais selon moi, ce gain de flexibilité est la plupart du temps utilisé pour de mauvaises raisons et tend à augmenter l'écart entre le modèle de données et la vue HTML. D'autres préoccupations sont abordées dans cet article (en anglais) : String Templating considered harmful

Pourquoi encore une autre bibliothèque ?

J'ai utilisé un tas de bibliothèques de templating client pour des projets professionnels et personnels. J'ai écrit cet article qui met en évidence les avantages et inconvénients de chacunes de ces bibliothèques selon moi. Puisqu'aucune ne me satisfaisait pleinement, j'ai décidé d'écrire ma propre bibliothèque. Je voulais un templating à faible logique basé sur le DOM, avec une syntaxe claire et déclarative comme Knockout, mais qui reste léger et ne fasse pas partie d'une plus grosse solution (je ne suis pas un grand fan des frameworks tout-en-un). J'ai trouvé le résultat suffisamment convaincant pour publier cette bibliothèque et la partager avec vous aujourd'hui.

Est-ce que c'est performant ?

Databinder pèse seulement 14 Ko (7.5KB en minifié, encore moins si compressé avec gzip). Tous les exemples de cette page ont été interprétés par Databinder. Avez-vous remarqué quelque-chose ? Cela devrait être suffisamment rapide pour que vous n'y voyez que du feu. A vrai dire, cela a pris exactement millisecondes.

Databinder est suffisament rapide et performant pour être utilisé sur des périphériques bas de gamme et même pour de petites animations. Aussi, l'un des points forts de Databinder est que vous choisissez exactement quelle partie du document actualiser et quand, ce qui aide beaucoup à l'optimisation.

Quels navigateurs sont supportés ?

Databinder est testé unitairement sur Internet Explorer 9 et au delà, ainsi que sur les dernières versions de Chrome, Firefox et certains navigateurs mobiles comme Android Stock Browser, Chrome Mobile, Firefox Mobile, Opera Mobile... En fait, ça devrait fonctionner sur n'importe quel navigateur décent sorti depuis IE9.

Est-ce que je peux l'utiliser avec ma bibliothèque / mon framework favori ?

Databinder n'a aucune dépendance et peut fonctionner seul. Cependant, vous pouvez facilement le connecter à d'autres bibliothèques. Par exemple, un adaptateur jQuery est disponible en téléchargement afin que vous puissez utiliser Databinder avec jQuery de cette manière: $("#myElement").databind(data);


Télécharger

Version actuelle : v0.7

Changelog
v0.7
gestion des chaînes littérales comme paramètres
v0.6
nouvelle API orientée fonctionnelle
ajout de la méthode get
v0.5
externalisation des extensions auparavant incluses
ajout d'extensions sur tableaux et booléens
v0.4
ajout d'extensions
changement des arguments d'appel des fonctions dans les templates
v0.3
ajout de la liaison template et des liaisons évènements
v0.2
nouvelle syntaxe pour les boucles
amélioration de la table de liaison automatique

Dépôt GitHub

Vous pouvez signaler des bogues et contribuer au projet sur le dépôt Github de Databinder.


Lier du contenu et des attributs

HTML

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

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

<a href="http://syllab.fr" title="syllab.fr">Mon site web</a>

L'attribut HTML data-bind spécifie toutes les liaisons entre votre modèle de données et l'élément HTML correspondant. La valeur doit être une liste de paires attribut:valeur séparées par des virgules. Si aucune correspondance n'est trouvée dans votre modèle de données, ou si la valeur associée vaut undefined ou null, aucune liaison n'est faite et l'élément reste inchangé.

La liaison text remplace le contenu de l'élément avec la valeur correspondante évaluée en tant que String avec échappement du code HTML. Si vous voulez ajouter du contenu HTML non échappé, utilisez la liaison html. Les liaisons href et title font référence aux attributs HTML respectifs. Tous les mots-clés de liaison ne faisant pas partie de cette liste et n'étant pas des évènements sont associés comme attributs à l'élément.


Boucles sur listes et tableaux

HTML

<h3>Morpion</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

Morpion

La liaison loop est utilisée pour parcourir les éléments d'une liste Array et répeter le contenu interne de l'élément pour chaque élément de la liste. Chaque itération crée un nouveau scope centré sur l'élement courant de la liste. Dans ce scope, vous pouvez récupérer l'index actuel avec la variable loopIndex et sa valeur avec la variable loopValue. Pour améliorer la lisibilité du template, vous pouvez renommer ces variables en spécifiant les attributs de liaison as pour la valeur et at pour l'index.

Si vous souhaitez supprimer l'élément HTML dans le cas où la liste est vide, ajoutez une liaison if: array.length


Parcourir les propriétés et index

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 }">
			Favori <b data-bind="num"></b>
		</li>
	</ol>
</div>
JavaScript

databind("#palette").set({
	palette: {
		"Bleu Ciel": "#ADD8E6",
		"Chocolat": "#D2691E",
		"Olive": "#808000",
		"Saumon": "#FA8072",
		"Indigo": "#4B0082"
	},
   favorites: [ "Chocolat", "Olive", "Saumon" ],
   getColorValue: function(data){
      return data.palette[this.color];
   }
});
Result
  1. Favori

La liaison loop vous permet également de parcourir toutes les propriétés énumérables d'un objet avec la syntaxe suivante: in objet, at clé, as valeur.


Conditions avec Booléens

HTML

<div id="feedback">
	<span data-bind="if: showSecret">Je ne devrais pas être là...</span>
	<input id="check" type="checkbox" data-bind="checked: understood"/>
	<label for="check">Okay j'ai pigé</label>
	<p>Est-ce que c'est dur jusqu'ici ?</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: "Trop facile" },
		{ text: "Tout va bien", default: true },
		{ text: "Il me faut un café !" }
	]
});
Result
Je ne devrais pas être là...

Est-ce que c'est dur jusqu'ici ?

Certains attributs d'éléments HTML tels que checked, selected ou disabled attendent des valeurs booléennes - les navigateurs les traitent comme true si ces attributs sont présents, peu importe leur valeur. Il y a également certaines liaisons spéciales qui attendent des booléens. Par exemple, if supprimera l'élement du code HTML généré si la valeur vaut false, tandis que visible appliquera style="display:none;" si false.

Lorsque la valeur passée à la liaison vaut undefined ou null, la liaison est ignorée et l'élément reste inchangé. Cependant, lorsque la valeur est un booléen, l'attribut est défini sans valeur si true et supprimé si false.


Jouer avec les fonctions

HTML

<h3>Nombres premiers dans la suite de Fibonacci</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

Nombres premiers dans la suite de Fibonacci

;

Quand la valeur associée est de type Function, cette fonction est exécutée dans le contexte du scope courant et la valeur retournée par la fonction est utilisée pour la liaison. Au sein de la fonction, this fait référence au scope courant. Le scope courant d'un noeud correspond aux données attachées au plus proche noeud parent avec une liaison de données. Dans l'exemple ci-dessus, le scope de l'élément span est la donnée attachée à l'élément p, c'est-à-dire suiteGenerator avec une variable 'number' déclarée comme itérateur.

Les arguments passés aux fonctions liées sont :

  1. le scope racine (l'objet passé comme argument à databind)
  2. l'élément HTML courant.

Lorsqu'une fonction est utilisée comme valeur d'une liaison loop, celle-ci se comporte alors comme un générateur : elle va itérer sur les valeurs de retour jusqu'à ce que la fonction retourne null. Utiliser des fonctions est très efficace et peut vous aider à implémenter des choses complexes telles que des générateurs ou de la récursivité. Si vous hésitez sur la manière d'implémenter un template, une fonction viendra probablement résoudre votre problème.


Rafraîchissement partiel ou total des vues

HTML

<p class="to-update">Vous avez passé
	<span data-bind="text: timePassed"></span> secondes sur cette page.
</p>
<p>Initialement cette valeur était
	<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

Vous avez passé secondes sur cette page.

Initialement cette valeur était :

La fonction reset doit être appelée après un premier appel de la fonction set. Son rôle est d'actualiser la valeur des liaisons pour un élément et ses éléments enfants. Pour que la liaison soit maintenue après un changement de valeur dans votre modèle, il faut avoir conservé une référence à cette valeur ; cela signifie que les variables de vos liaisons ne peuvent pas être des primitives (booléen, nombre, string) sinon la référence sera perdue lors de l'assignation d'une nouvelle valeur. Pensez à manipuler des objets plutôt que des primitives pour profiter de la mise à jour automatique des liaisons, comme le montre l'exemple.

Un avantage de Databinder comparé aux solutions de templating basées sur les String est que vous pouvez facilement mettre à jour une sous-partie d'un template sans toucher au reste. Il est également possible d'actualiser des liaisons issues de plusieurs appels distincts à set en un seul appel de reset sur un élément parent.


Réagir aux évènements

HTML

<div id="mylist">
	<h3>Liste dynamique (actuellement
		<span data-bind="itemCollection.length"></span> éléments)
	</h3>
	<ul data-bind="loop: itemCollection">
		<li>
			Element <span data-bind="num"></span> - <a data-bind="click: remove">supprimer</a>
		</li>
	</ul>
	<input type="button" value="Ajouter un autre élément"	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();
Résultat

Liste dynamique (actuellement éléments)

Vous savez déjà comment réagir aux évènements via les attributs on[evenement] tels que onclick. Mais dans la fonction callback, vous devez manuellement retrouver la donnée dans votre modèle associée à cet élément. L'autre option est de déclarer l'écoute d'évènements depuis vos contrôleurs JavaScript, avec addEventListener par exemple. Toutefois, quand les éléments HTML concernés sont générés dynamiquement par un template, vous devez soit utiliser des évènements délégués, soit déclarer à nouveau toutes les écoutes d'évènement après le rendu du template. C'est assez fastidieux, c'est pourquoi les liaisons évènement ont été ajoutées pour vous permettre de déclarer facilement une écoute d'évènement depuis vos templates juste après que les éléments correspondants aient été créés.

Tous les évènements DOM sont supportés. D'aucuns diront que cela casse la séparation vue/contrôleur, à vous de décider si vous souhaitez utiliser ces liaisons. En tout cas, ça rend parfois les choses beaucoup plus simples.

Dans les fonctions callbacks, this fait référence au scope courant tout comme les autres fonctions dans les templates. Les arguments passés à ces fonctions sont :

  1. l'objet évènement
  2. le scope racine (celui passé en argument à databind)
  3. l'élément HTML courant

Enregistrer les saisies utilisateur depuis un formulaire

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">
	Le gagnant est <span data-bind="name"></span> avec <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

Feuille des scores

Le gagnant est avec points.

La fonction get a l'effet inverse d'un reset. Elle s'utilise elle-aussi près un premier appel à set, mais au lieu de mettre à jour le document selon les modifications du modèle, elle met à jour le modèle selon les modifications dans le document. Actuellement, cela ne s'applique qu'aux liaisons value des éléments <input> et au contenu des <textarea>. Cette méthode est particulièrement utile pour les formulaires d'édition où l'on peut mettre à jour dans un sens ou dans l'autre les informations.


Des templates réutilisables

HTML

<section id="flags">
	<h1>Drapeaux par continent </h1>
	<h2>Europe</h2>
	<ul data-bind="europe">
	    <li data-bind="template: flag"></li>
	</ul>
   <h2>Asie</h2>
   <ul data-bind="asia">
      <li data-bind="template: flag"></li>
   </ul>
   <h2>Afrique</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>Capitale: </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" }
	]
});
            
Résultat

Drapeaux par continent

Europe

Asie

Afrique

Les liaisons template vous permettent de réutiliser des fragments de code HTML avec éventuellement d'autres liaisons plutôt que d'avoir du code redondant. Cela aide à rendre vos templates plus légers, lisibles et faciles à maintenir. Cette liaison attend comme valeur l'attribut id d'un élément HTML <template> dans le document. Le contenu HTML de ce template sera copié à l'intérieur de l'élément courant et le processus de data-binding continuera dans le même scope.

Les éléments <template> doivent impérativement être déclarés en dehors de tout élément avec une liaison de données. Les templates peuvent être encapsulés l'un dans l'autre et modifiés entre deux appels de databind.


Liste des liaisons particulières

Si une liaison n'est pas dans cette liste et n'est pas une liaison évènement, alors elle sera assignée comme attribut à l'élément HTML courant.


Omission de l'attribut de liaison: Liaison par défaut

Quand l'attribut n'est pas spécifié dans une déclaration de liaison, une liaison par défaut est choisie selon l'élément et le type de la valeur correspondante.

Element Type de donnée Liaison par défaut
tous Object with
tous Array loop
tous Element html
<input type="checkbox">, <input type="radio"> Boolean checked
<option> Boolean selected
autres éléments Boolean if
<audio>, <embed>, <img>, <iframe>, <script>, <source>, <track>, <video> String src
Autres types <input> String / Number value
autres éléments String / Number text

Omission de l'attribut et de la valeur de liaison: Liaison automatique

Quand l'attribut data-bind est spécifié sans aucune valeur, Databinder va essayer de deviner à quelle valeur vous faites référence en vérifiant les attributs id, name et class de l'élément, dans cet ordre. Ensuite, la liaison par défaut s'appliquera selon le type de la variable correspondante.

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>
Liaison automatique
  1. ul a comme ID #people
  2. people est défini dans les données comme Array
  3. liaison appliquée à ul: loop: people
  1. li n'a pas d'ID ni de name
  2. li a la classe .item mais item n'est pas défini dans le scope courant
  3. li a la classe .name et name est defini en tant que String
  4. liaison appliquée à chaque li: text: name

Declarer des extensions pour étendre la syntaxe de vos templates

Les extensions sont des fonctions utilitaires appliquées sur les valeurs trouvées via une liaison. Ils sont utiles pour ajouter des instructions logiques de base, pour afficher une variable dans un format particulier ou pour des options de tri/sélection sur les listes. Vous pouvez appliquer une extension à une valeur en écrivant |extensionName derrière le nom de la variable dans la liaison.

Plusieurs extensions peuvent être appliquées les unes à la suite des autres, en les enchaînant de cette manière :

data-bind="text: sentence | trim | capitalize"

Aussi, une extension peut recevoir des nombres ou des mots comme paramètres, séparés par des espaces après le nom de l'extension :

data-bind="if: sum | between 5 10"
data-bind="loop: messages | filter byDate"

Ajouter vos propres extensions est très simple. Admettons que vous voulez utiliser la super librairie de formattage de date Moment.js. Déclarez l'extension comme ceci:

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: "Auteur",
	date: Date.now(),
	message: "Bonjour Databinder !"
});
Résultat


Quelques exemples d'extensions

Aucune extension n'est fournie de base avec DataBinder. Une liste d'extensions vous est proposée ci-dessous, afin que vous puissiez choisir juste celles qui vous intéressent et apprendre comment ajouter les votres. Copiez simplement le code de l'extension à la fin du fichier source de Databinder ou dans un script à part, à votre convenance.


Exemples avancés

Mise à jour par intervalles

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();
Résultat

Templates récursifs

Un template peut faire référence à lui-même pour les vues basées sur la récursivité.

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: "Vacances 2014",
			isOpened: false,
			items: [{ name: "photo1.jpg" },
	              { name: "photo2.jpg" }]
		},{name: "paysage.png" }]
	},{
		name: "Musique",
		isOpened: true,
		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();
	}
});
Résultat
Loading tree view...

Aides à l'internationalisation

L'internationalisation d'un site amène de nombreuses problématiques qui sont résolues par diverses solutions existantes côté serveur ou client. Certaines de ces problématiques sont directement reliées aux templates, telles que le formattage de dates ou de sommes monétaires. Vous pouvez à cet effet déclarer quelques extensions pour vous aider à connecter vos templates aux API d'internationalisation existantes.

(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();
   }
});
Résultat

Your account activity

Date Description Amount

TodoMVC App

L'exemple classique de la to-do-list reprenant celui du site todomvc.com


Essayez par vous-même !

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