<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>
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.
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.
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
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.
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.
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.
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);
get
Vous pouvez signaler des bogues et contribuer au projet sur le dépôt Github de Databinder.
<a data-bind="text: label, href: url, title: tooltip"></a>
var thatLinkElement = document.querySelector("a");
databind(thatLinkElement).set({
label: "Mon site web",
url: "http://syllab.fr",
tooltip: "syllab.fr"
});
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.
<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>
databind("table").set({
grid: [ ["X", " ", "O" ],
["O", "X", " " ],
[" ", " ", "X" ] ]
});
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
<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>
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];
}
});
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.
<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>
databind("#feedback").set({
understood: true,
showSecret: false,
answers: [
{ text: "Trop facile" },
{ text: "Tout va bien", default: true },
{ text: "Il me faut un café !" }
]
});
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
.
<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>
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;
}
});
;
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 :
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.
<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>
var dataReference = { timePassed: 0 };
databind("body").set(dataReference);
setInterval(function(){
dataReference.timePassed++;
databind("p.to-update").reset();
}, 1000);
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.
<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>
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();
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 :
<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>
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();
}
});
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.
<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>
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" }
]
});
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
.
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.
Object
, Array
, String
ou groupe de liaison
ajoute ou retire les classes indiquées à l'élément courant
<input data-bind="class: properties" />
<button data-bind="class: classNames">Cliquez ici</button>
<section class="anciennes classes" data-bind="class: otherClasses"></section>
<p class="truc" data-bind="text: message, class: { important: isImportant, boulot: isBusiness }"></p>
databind('input').set({
properties: {
bleu: false,
rouge: true
}
});
databind('button').set({
classNames: "plusieurs classes en string"
});
databind('section').set({
otherClasses: ["ou", "en", "liste"]
});
databind('p.truc').set({
message: "C'est un truc important !",
isImportant: true,
isBusiness: false
});
<input class="rouge" />
<button class="plusieurs classes en string">Click me</button>
<section class="ou en liste"></section>
<p class="truc important">
C'est un truc important !
</p>
Boolean
cache l'élément courant en lui appliquant style="display:none;"
si true
<ol data-bind="loop: items">
<li data-bind="text: loopValue, hidden: autocensor"></li>
</ol>
databind('ol').set({
items: ["Certains", "éléments", "semblent", "avoir", "disparu"],
autocensor: function(){
return this.loopIndex % 2 === 0;
}
});
String
ou HTMLElement
remplace le contenu HTML de l'élément courant. Attention aux attaques XSS ! Si le contenu inséré ne provient pas d'une source fiable, vous devriez plutôt utiliser la liaison text.
<p data-bind="html: message">contenu perdu après rendu du template</p>
databind('p').set({
message: "Il me faut <strong style='color:blue;'>du contenu HTML non échappé</strong> ici."
});
contenu perdu après rendu du template
Boolean
supprime l'élément courant et stoppe le traitement des noeuds enfants si false
<div>
<p data-bind="if: results|none">Aucun résultat<p>
<ul data-bind="if: results|some, loop: results">
<li data-bind="loopValue"></li>
</ul>
</div>
databind('div').set({
results: []
});
Aucun résultat
<div>
<p>Aucun résultat<p>
</div>
Boolean
inverse de la liaison if ; supprime le noeud courant si true
Array
ou Object
répète le contenu interne de l'élément courant pour chaque élément de la liste. Chaque itération crée un scope avec des variables loopValue et loopIndex pouvant être renommées en spécifiant les attributs as et at dans le groupe de liaison.
Code d'exemple avec Array
Code d'exemple avec Object
Object
, String
ou groupe de liaison
ajoute ou retire des déclarations CSS dans l'attribut style de l'élément courant
<input style="background-color: lime;" data-bind="style: otherRules" />
<button data-bind="style: ruleSet">Cliquez ici</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: "Gros truc rouge !",
color: "rgb(255,0,0)",
size: "3em"
});
String
remplace le contenu texte de l'élément courant en échappant le HTML
<p data-bind="text: message">perdu après rendu du template</p>
databind('p').set({
message: "Pas d'inquiétudes pour les failles <iframe>XSS</iframe>"
});
perdu après rendu du template
template
importe le contenu d'un <template>
dans l'élément courant et continue le rendu du template dans le même scope
Code d'exemple
Boolean
inverse de la liaison hidden ; cache l'élément courant en appliquant style="display:none;"
si false
<ol data-bind="loop: items">
<li data-bind="text: loopValue, visible: autocensor"></li>
</ol>
databind('ol').set({
items: ["Certains", "éléments", "semblent", "avoir", "disparu"],
autocensor: function(){
return this.loopIndex % 2 === 0;
}
});
Object
crée un nouveau scope centré sur l'objet passé en paramètre
Mon nom est , mon fils s'appelle et mon petit-fils est
Mon nom est , mon fils s'appelle et mon petit-fils est
Mon nom est , mon fils s'appelle et mon petit-fils est
Mon nom est , mon fils s'appelle et mon petit-fils est
databind(document.body).set({
name: "Karl",
child : {
name: "Karl Junior",
child: {
name: "Baby Karl"
}
}
});
Mon nom est , mon fils s'appelle et mon petit-fils est
Mon nom est , mon fils s'appelle et mon petit-fils est
Mon nom est , mon fils s'appelle et mon petit-fils est
Mon nom est , mon fils s'appelle et mon petit-fils est
La résolution d'un nom de variable dans un scope de données fonctionne de manière similaire aux closures en JavaScript: quand une valeur vaut undefined
dans l'objet courant, Databinder va remonter la chaîne du scope de parent en parent jusqu'à trouver une valeur qui corresponde. Cependant, si vous spécifiez un nom de variable commençant par un ou plusieurs points, vous prévenez la résolution automatique et indiquez précisément à quel niveau du scope regarder.
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
|
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.
<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
a comme ID #people
people
est défini dans les données comme Array
ul
: loop: people
li
n'a pas d'ID ni de name
li
a la classe .item
mais item
n'est pas défini dans le scope courantli
a la classe .name
et name
est defini en tant que String
li
: text: name
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();
};
<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: "Auteur",
date: Date.now(),
message: "Bonjour Databinder !"
});
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.
true
si value == param
databind.extensions.equals = function(x){ return this == x; };
true
si value > Number
(ou value.length
pour les listes)
databind.extensions.moreThan = function(n){
return (Array.isArray(this) ? this.length : +this) > n;
};
true
si value >= start && value <= end
(ou value.length
pour les listes)
databind.extensions.between = function(start, end){
var n = (Array.isArray(this) ? this.length : +this);
return n >= start && n <= end;
};
true
si chaque élément de la liste a une propriété p
assimilable à true
ou a retourné true
à travers la fonction f
databind.extensions.every = function(f){
return Array.isArray(this) && this.every(typeof f == "function" ? f : function(){ return this[f] });
};
true
si liste non vide, ou si au moins un élément de la liste a une propriété p
vraie ou a retourné true
à travers la fonction f
databind.extensions.some = function(f){
if(f === undefined) return Array.isArray(this) && this.length > 0;
return Array.isArray(this) && this.some(typeof f == "function" ? f : function(){ return this[f] });
};
true
si null
, undefined
, liste vide ou si aucun des éléments de la liste passé dans la fonction en paramètre n'a retourné 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)
avec comme paramètre optionnel le nombre de décimales à conserver.
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.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();
Un template peut faire référence à lui-même pour les vues basées sur la récursivité.
<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: "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();
}
});
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);
<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 |
---|---|---|
L'exemple classique de la to-do-list reprenant celui du site todomvc.com
<div id="test-zone">
</div>
databind("#test-zone").set({
});