在Mustache中,如何获取当前部分的索引?

62

我正在使用 Mustache 并使用数据。

{ "names": [ {"name":"John"}, {"name":"Mary"} ] }

我的mustache模板是:

{{#names}}
    {{name}}
{{/names}}

我想要做的是能够获取数组中当前数字的索引。例如:

{{#names}}
    {{name}} is {{index}}
{{/names}}

并且将其打印出来

John is 1
Mary is 2

使用Mustache、Handlebars或其他扩展能否获得这个结果?


在O(n)时间内运行... Mustache.render('{{#list}}{{index}}{{/list}}', { list: ['a', 'b', 'c' ], index: () => (++this.i || (this.i = 0)) }); - tim-montague
13个回答

51

这是我在JavaScript中的做法:

var idx = 0;

var data = { 
   "names": [ 
       {"name":"John"}, 
       {"name":"Mary"} 
    ],
    "idx": function() {
        return idx++;
    }
};

var html = Mustache.render(template, data);

你的模板:

{{#names}}
    {{name}} is {{idx}}
{{/names}}

10
唯一的问题是,您必须记住只获取一次 idx ,否则它将更改当前对象的索引。在我们的某些模板中,我们在当前对象的多个位置使用该索引。 - christophercotton
很棒的答案,我还添加了一个适用于多个{{idx}}的解决方案。 - Roman
多个 {{index}} 的解决方案可以在 Mustache.js 中数组元素的索引 找到。@oyatek - Alexar
我在模板视图中使用一个函数,该函数使用indexOf返回索引,请查看我的答案获取更多详细信息。 - Pierre

31

参考文献:此功能现已内置于Handlebars中,可与Mustache兼容。

使用{{@index}}

{{#names}}
    {{name}} is {{@index}}
{{/names}}

约翰是0

玛丽是1


29
Handlebars与Mustache兼容是一个普遍的误解。实际上,Handlebars根本不兼容Mustache,它们不能相互替代。我想澄清这一点,因为我曾试图将它们混用。 - derickito
6
仅凭胡须就能完成它吗? - baraber
1
Handlebars与mustache完全不兼容。尽管他们在网站上这样说,但这是完全错误的。 - derickito
5
这个答案不应该被接受,因为它没有回答标题中的问题。 - Luke Skywalker
2
这是一个糟糕的答案。Moustache不是特定于平台的,而Handlebars是。 - dajek
显示剩余2条评论

12
在handlebars.js中,您可以通过使用帮助函数来实现此目的。 (实际上,这里提到的http://yehudakatz.com/2010/09/09/announcing-handlebars-js/ handlebars的优点之一是,您可以使用帮助函数而无需在调用模板之前重写对象。)
因此,您可以这样做:
  var nameIndex = 0;
  Handlebars.registerHelper('name_with_index', function() {
    nameIndex++;
    return this.name + " is " + nameIndex;
  })

然后,你的模板可以是这样的:

{{#names}}
<li>{{name_with_index}}</li>
{{/names}}

您的数据与以前相同,即:

{ "names": [ {"name":"John"}, {"name":"Mary"} ] };

然后您会得到这个输出:

<li>John is 1</li>
<li>Mary is 2</li>

为了使其真正起作用,每次呈现模板时都需要重置nameIndex,因此您可以在列表开头设置重置助手。因此完整的代码看起来像这样:
  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);

(这样可以正确地将模板渲染两次。)

那可能行得通,但它有一个问题,即您不能在项目期间在多个位置使用索引。由于每次检索索引时都会将其递增。这对于模板设计师来说不起作用,并且会导致错误和问题。此外,拥有“reset_index”会添加一种逻辑方法来重置索引,我认为这与无逻辑模板相违背。我已经在Android版本中看到了他们可用的{{-index}},它可以做正确的事情。 - christophercotton

7

您可以在对象列表上运行以下循环。

该解决方案具有以下优点:

  • 不更改模型数据
  • 可以多次访问索引

代码:

for( var i=0; i< the_list.length; i++) {
    the_list[i].idx = (function(in_i){return in_i+1;})(i);
}

解释:

使用函数代替变量名,以避免数据被改变。闭包用于返回在循环中创建函数时'i'的值,而不是循环结束时'i'的值。


6
更好的处理Mustache模板的方法是使用一个函数,该函数使用indexOf获取索引值:
var data = { 
   names: [ {"name":"John"}, {"name":"Mary"} ],
    index: function() {
        return data.names.indexOf(this);
    }
};

var html = Mustache.render(template, data);

在您的模板中:

{{#names}}
    {{name}} is {{index}}
{{/names}}

缺点是这个index函数中的数组使用硬编码的data.names,为了使其更具动态性,我们可以使用另一个类似于以下的函数:
var data = { 
   names: [ {"name":"John"}, {"name":"Mary"} ],
    index: function() {
        return function(array, render) {
            return data[array].indexOf(this);
        }
    }
};

var html = Mustache.render(template, data);

在您的模板中使用:

{{#names}}
    {{name}} is {{#index}}names{{/index}}
{{/names}}

还有一个小缺点是在这个例子中你需要传递数组名称到索引函数中,如names{{#index}}names{{/index}}


2
另一个问题是该算法的时间复杂度为O(N^2),这可能会在中等大小的列表上引起问题。每次获取一个项目的索引时,都必须搜索整个列表。 - Steve Cooper

4

非常棒的助手在这里:

// {{#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


1
谢谢,这个可以用,但我根据我的使用情况进行了一些扩展 https://gist.github.com/1429324 - Chad Cache
感谢。一个扩展索引属性的简单实现。我正在使用它。 - John Dhom
GRMustache(用于Objective-C的Mustache)也可以处理这种情况:https://github.com/groue/GRMustache/blob/master/Guides/sample_code/indexes.md - Gwendal Roué

3

推荐解决方案


我的建议是添加一个index键。
let data = {
  "names": [
    {"name": "John"}, 
    {"name": "Mary"}
  ]
};

// Add an index manually
data = {
  names: [
    { index: 0, name: "John" },
    { index: 1, name: "Mary" }
  ]
};

// Add an index with a loop
data = data.names.map((name, index) => name.index = index);

// Nested arrays with indices (parentIndex, childIndex)
data = {
  names: [{
    parentIndex: 0,
    name: "John",
    friends: [{
      childIndex: 0,
      name: "Mary"
    }]
  }]
};

提议的胡子指数解决方案存在问题。

其他提出的解决方案并不如下所述那样简洁...

@Index

Mustache 不支持 @Index

IndexOf

此解决方案运行时间为 O(n^2),因此性能不是最好的。而且每个列表都必须手动创建索引。

const data = {
  list: ['a', 'b', 'c'],
  listIndex: function () {
    return data.list.indexOf(this);
  }
};

Mustache.render('{{#list}}{{listIndex}}{{/list}}', data);

全局变量
这个解决方案的运行时间为O(n),因此性能更好,但也会“污染全局空间”(即向window对象添加属性,并且内存直到浏览器窗口关闭才被垃圾回收器释放),而且必须手动为每个列表创建索引。
const data = {
  list: ['a', 'b', 'c'],
  listIndex: function () {
    return (++window.listIndex || (window.listIndex = 0));
  }
};

Mustache.render('{{#list}}{{listIndex}}{{/list}}', data);

局部变量

这个解决方案在O(n)的时间内运行,不会“污染全局空间”,也不需要为每个列表手动创建。然而,这个解决方案比较复杂,对于嵌套列表的处理效果不佳。

const data = {
  listA: ['a', 'b', 'c'],
  index: () => name => (++data[`${name}Index`] || (data[`${name}Index`] = 0))
};

Mustache.render('{{#listA}}{{#index}}listA{{/index}}{{/listA}}', data);

1
如果您可以控制JSON字符串的输出,那么可以尝试这个方法。
{ "names": [ {"name":"John", "index":"1"}, {"name":"Mary", "index":"2"} ] }

因此,当您创建JSON字符串时,请为每个对象添加索引作为另一个属性。


有没有办法在不添加索引属性到数据的情况下完成它?我获取的数据中没有它,而且我只取其中的一个子集。我希望能免费获得一个索引。 - christophercotton
很不幸,似乎没有。你可以在运行模板之前在JS中迭代它,并添加索引。或者查看源代码,看看是否可以添加一些内容来返回当前索引。 - sciritai
好的,我会将此问题保持开放几天,看看是否有其他解决方案。否则我会将这个标记为正确答案。 - christophercotton
下面有更好的解决方案,你应该改变你选择的答案。 - lukemh
你指的是哪个解决方案?除了Handlebars现在对索引的本地支持之外,许多其他解决方案只允许检索一次索引。这并不是一个很好的解决方案,特别是因为我们需要在模板中多次插入索引。 - christophercotton

1
(在node 4.4.7和moustache 2.2.1中测试通过。)如果您想要一种漂亮、干净、功能性的方法来实现它,而不涉及全局变量或改变对象本身,请使用此函数;
var withIds = function(list, propertyName, firstIndex) {
    firstIndex |= 0;
    return list.map( (item, idx) => {
        var augmented = Object.create(item);
        augmented[propertyName] = idx + firstIndex;
        return augmented;
    })
};

使用它来组装您的视图;
var view = {
    peopleWithIds: withIds(people, 'id', 1) // add 'id' property to all people, starting at index 1
};

这种方法的好处在于它创建了一组新的“视图模型”对象,使用旧集合作为原型。您可以像读取person.firstName一样读取person.id。但是,此函数不会改变您的people对象,因此其他代码(可能依赖于ID属性不存在)不会受到影响。
算法的时间复杂度为O(N),非常快速。

1
只要您不进入超过一个数组,就可以将索引存储在辅助器上,并在上下文更改时递增。我正在使用类似于以下内容的东西:
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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接