J'utilise Moustache et j'utilise les données
{ "names": [ {"name":"John"}, {"name":"Mary"} ] }
Mon modèle de moustache est:
{{#names}}
{{name}}
{{/names}}
Ce que je veux pouvoir faire, c'est obtenir un index du nombre actuel dans le tableau. Quelque chose comme:
{{#names}}
{{name}} is {{index}}
{{/names}}
et le faire imprimer
John is 1
Mary is 2
Est-il possible d'obtenir cela avec Moustache? ou avec un guidon ou une autre extension?
À titre de référence, cette fonctionnalité est désormais intégrée au guidon qui est compatible avec Moustache.
Utilisez {{@index}}
{{#names}}
{{name}} is {{@index}}
{{/names}}
John est 0
Marie a 1
Voici comment je le fais en JavaScript:
var idx = 0;
var data = {
"names": [
{"name":"John"},
{"name":"Mary"}
],
"idx": function() {
return idx++;
}
};
var html = Mustache.render(template, data);
Votre template:
{{#names}}
{{name}} is {{idx}}
{{/names}}
Dans handlebars.js, vous pouvez accomplir cela avec une fonction d'assistance. (En fait, l'un des avantages mentionnés à propos du guidon ici/ http://yehudakatz.com/2010/09/09/announcing-handlebars-js/ est que vous pouvez utiliser des assistants au lieu de devoir réécrire les objets appeler le modèle.
Donc, vous pouvez faire ceci:
var nameIndex = 0;
Handlebars.registerHelper('name_with_index', function() {
nameIndex++;
return this.name + " is " + nameIndex;
})
Et puis votre modèle peut être ceci:
{{#names}}
<li>{{name_with_index}}</li>
{{/names}}
Vos données sont les mêmes que précédemment, à savoir:
{ "names": [ {"name":"John"}, {"name":"Mary"} ] };
Et vous obtenez cette sortie:
<li>John is 1</li>
<li>Mary is 2</li>
Pour que cela fonctionne vraiment, nameIndex doit être réinitialisé chaque fois que le modèle est rendu. Pour ce faire, vous pouvez avoir un assistant de réinitialisation au début de la liste. Donc, le code complet ressemble à ceci:
var data = { "names": [ {"name":"John"}, {"name":"Mary"} ] };
var templateSource = "<ul>{{reset_index}}{{#names}}<li>{{name_with_index}}</li>{{/names}}</ul>";
var template = Handlebars.compile(templateSource);
var helpers = function() {
var nameIndex = 0;
Handlebars.registerHelper('name_with_index', function() {
nameIndex++;
return this.name + " is " + nameIndex;
});
Handlebars.registerHelper('reset_index', function() {
nameIndex = 0;
})
}();
var htmlResult= template(data);
$('#target').html(htmlResult);
var htmlResult2= template(data);
$('#target2').html(htmlResult2);
(Cela peut correctement rendre le modèle deux fois.)
Vous pouvez exécuter la boucle suivante sur votre liste d'objets.
Cette solution présente les avantages suivants:
Code:
for( var i=0; i< the_list.length; i++) {
the_list[i].idx = (function(in_i){return in_i+1;})(i);
}
Explication:
Une fonction est utilisée à la place d'un nom de variable, les données ne sont donc pas mutées. La fermeture est utilisée pour retourner la valeur de 'i' au moment où la fonction est créée dans la boucle au lieu de la valeur de i à la fin de la boucle.
Une meilleure approche pour Moustache serait d’utiliser une fonction qui obtient l’index en utilisant indexOf
:
var data = {
names: [ {"name":"John"}, {"name":"Mary"} ],
index: function() {
return data.names.indexOf(this);
}
};
var html = Mustache.render(template, data);
Dans votre modèle:
{{#names}}
{{name}} is {{index}}
{{/names}}
L'inconvénient est que cette fonction index
a le tableau data.names
codé en dur pour le rendre plus dynamique, nous pouvons utiliser une autre fonction du genre:
var data = {
names: [ {"name":"John"}, {"name":"Mary"} ],
index: function() {
return function(array, render) {
return data[array].indexOf(this);
}
}
};
var html = Mustache.render(template, data);
Utilisation dans votre modèle:
{{#names}}
{{name}} is {{#index}}names{{/index}}
{{/names}}
Un petit inconvénient est également que vous devez passer le nom du tableau à la fonction d'index dans cet exemple names
, {{#index}}names{{/index}}
.
Grande aide ici:
// {{#each_with_index records}}
// <li class="legend_item{{index}}"><span></span>{{Name}}</li>
// {{/each_with_index}}
Handlebars.registerHelper("each_with_index", function(array, fn) {
var buffer = "";
for (var i = 0, j = array.length; i < j; i++) {
var item = array[i];
// stick an index property onto the item, starting with 1, may make configurable later
item.index = i+1;
// show the inside of the block
buffer += fn(item);
}
// return the finished buffer
return buffer;
});
Source: https://Gist.github.com/1048968
Mon principal cas d'utilisation était la possibilité de placer une classe «active» sur des éléments. J'ai déconné de combiner une aide "égale" avec une aide "index_for_each", mais c'était trop compliqué.
Au lieu de cela, je suis venu avec l'aide de base "active" suivante. C'est très spécifique, mais c'est un cas d'utilisation courant pour n'importe quel scénario de menu/liste de sélection:
Usage:
<ul>
{{{active 2}}}
{{#each menuItem}}
<li class="{{{active}}}">{{this}}</li>
{{/each}}
</div>
Le troisième élément de menu aurait-il une "classe = 'active'".
L'assistant est ici (c'est la version de CoffeeScript):
active = 0
cur = 0
handlebars.registerHelper 'active', (item, context) ->
if arguments.length == 2
active = item
cur = 0
''
else if cur++ == active
'active'
(Testé au noeud 4.4.7, moustache 2.2.1.)
Si vous voulez une manière fonctionnelle propre et agréable de le faire, qui n'implique pas de variables globales ni de la mutation des objets eux-mêmes, utilisez cette fonction;
var withIds = function(list, propertyName, firstIndex) {
firstIndex |= 0;
return list.map( (item, idx) => {
var augmented = Object.create(item);
augmented[propertyName] = idx + firstIndex;
return augmented;
})
};
Utilisez-le lorsque vous assemblez votre vue.
var view = {
peopleWithIds: withIds(people, 'id', 1) // add 'id' property to all people, starting at index 1
};
Le bon côté de cette approche est qu’elle crée un nouvel ensemble d’objets 'viewmodel', en utilisant l’ancien ensemble comme prototypes. Vous pouvez lire le person.id
comme vous le feriez pour person.firstName
. Cependant, cette fonction ne modifie pas du tout vos objets personnes. Par conséquent, aucun autre code (qui aurait pu être dépendant de l'absence de la propriété ID) ne sera pas affecté.
L'algorithme est O (N), donc agréable et rapide.
Si vous pouvez contrôler la sortie de la chaîne JSON, essayez ceci.
{ "names": [ {"name":"John", "index":"1"}, {"name":"Mary", "index":"2"} ] }
Ainsi, lorsque vous créez votre chaîne JSON, ajoutez l'index comme une autre propriété pour chaque objet.
Tant que vous ne voyagez pas dans plus d'un tableau, vous pouvez stocker l'index sur l'assistant et simplement l'incrémenter lorsque le contexte change. J'utilise quelque chose de similaire à ce qui suit:
var data = {
foo: [{a: 'a', 'b': 'a'}, {'a':'b', 'b':'b'}],
i: function indexer() {
indexer.i = indexer.i || 0;
if (indexer.last !== this && indexer.last) {
indexer.i++;
}
indexer.last = this;
return String(indexer.i);
}
};
Mustache.render('{{#foo}}{{a}}{{i}}{{b}}{{i}}{{/foo}}', data);
Ma recommandation est d'ajouter une clé index
let data = { "names": [ {"name":"John"},
{"name":"Mary"} ] };
// Add an index manually
data = {
names: [
{ id: 0, name: "John" },
{ id: 1, name: "Mary" }
]
};
// Add an index with a loop
data = data.names
.map(function (name, i) {
name.id = i;
return name;
});
// Nested arrays with indices (parentId, childId)
data = {
names: [{
parentId: 0,
name: "John",
friends: [{
childId: 0,
name: "Mary"
}]
}]
};
Les autres solutions proposées ne sont pas aussi propres et souffrent des quelques problèmes décrits ci-dessous.
@Index
Moustache ne supporte pas @ Index
Indice de
Cette solution s'exécute en 0 (n ^ 2) fois et ne présente donc pas les meilleures performances. De plus, l'index doit être créé manuellement pour chaque liste.
const data = {
list: ['a', 'b', 'c'],
listIndex: function () {
return data.list.indexOf(this);
}
};
Mustache.render('{{#list}}{{listIndex}}{{/list}}', data);
Variable globale
Cette solution fonctionne dans O(n) time, donc de meilleures performances ici, mais "pollue également l'espace global" (c'est-à-dire que les propriétés ajoutées à l'objet window
, ne sont pas récupérées tant que la fenêtre du navigateur n'est pas fermée) et l’index doivent être créés manuellement pour chaque liste.
const data = {
list: ['a', 'b', 'c'],
listIndex: function () {
return (++window.listIndex || (window.listIndex = 0));
}
};
Mustache.render('{{#list}}{{listIndex}}{{/list}}', data);
Variable locale
Cette solution s'exécute dans O(n) time, ne "pollue pas l'espace global" et n'a pas besoin d'être créée manuellement pour chaque liste. Cependant, la solution est complexe et ne fonctionne pas bien avec les listes imbriquées.
const data = {
listA: ['a', 'b', 'c'],
index: () => name => (++data[`${name}Index`] || (data[`${name}Index`] = 0))
};
Mustache.render('{{#listA}}{{#index}}listA{{/index}}{{/listA}}', data);
<script>var index = 1;</script>
{{#names}}
{{name}} is <script>document.write(index++);</script>
{{/names}}
Cela fonctionne parfaitement bien.