如何从 Ember 中的模板调用控制器函数?

How do I call a controller function from a template in Ember?

假设我有一个迭代项目集合的模板,我想为每个特定于控制器的项目调用一个函数,而不是模型级问题:

{{#each people as |person|}}
  icon name: {{findIconFor(person)}}
{{/each}}

我想在控制器中定义 findIconFor,因为这是特定视图的特定内容。

export default Ember.Controller.extend({
  findIconFor: function(person) {
    // figure out which icon to use
  }
);

但这不起作用。模板编译失败。解析错误:预期 'STRING'、'NUMBER'、'ID'、'DATA'、得到 'INVALID'

这样做的 "ember way" 是什么?

我会在控制器中使用计算的 属性:

iconPeople: Ember.computed('people.@each', function(){
  var that = this;
  return this.get('people').map(function(person){
    return {
      'person': person,
      'icon': that.findIconFor(person)
    };
  });
})

现在您可以从 {{person.icon}} 获取图标,从 {{person.person.name}} 获取名称。您可能想对此进行改进(并且代码未经测试),但这是一般的想法。

如果图标与人相关联,那么由于人是由模型表示的,因此最好将其实现为人模型上的计算 属性。您尝试将其放入控制器的意图是什么?

// person.js
export default DS.Model.extend({
  icon: function() { return "person-icon-" + this.get('name'); }.property('name')
  ..
};

然后假设peopleperson的数组:

{{#each people as |person|}}
  icon name: {{person.icon}}
{{/each}}

@jnfingerle 提供的替代方案有效(我假设您发现他建议您循环 iconPeople),但创建一个新数组似乎需要做很多额外的工作包含对象。图标是否依赖于只有控制器知道的任何东西?如果不是,正如我所说,为什么计算它的逻辑应该在控制器中?

把东西放在哪里是一个哲学和偏好问题。有些人喜欢只包含来自服务器的字段的基本模型;其他人计算模型中的状态和中间结果。有些人在控制器中放了很多东西,而另一些人更喜欢 "services" 中具有更多逻辑的轻量级控制器。就个人而言,我支持更重的模型、更轻的控制器和服务。当然,我并不是说业务逻辑、繁重的数据转换或视图准备应该放在模型中。但请记住,模型代表一个对象。如果对象有一些有趣的方面,无论它来自服务器还是以某种方式计算,对我来说将它放入模型中很有意义。

还请记住,控制器是紧密耦合 route/controller/view 关系的一部分。如果您在一个控制器中计算了一些特定于模型的东西,那么您可能必须将它添加到恰好正在处理相同模型的其他控制器中。然后在你知道之前,你正在编写控制器混合,这些混合在控制器之间共享逻辑,而这些控制器一开始就不应该存在于其中。

不管怎样,你说你的图标来自"unrelated data store"。这听起来是异步的。对我来说,这暗示它可能是一个名为 PersonIcon 的子模型,它是 person 模型中的 belongsTo。您可以使用适用于该模型的适配器和序列化器的正确组合来使其工作。这种方法的好处是,在创建 person 模型时,或者当您实际需要图标时(如果您说 async: true).

但也许您没有使用 Ember 数据,或者不想遇到那么多麻烦。在这种情况下,您可以考虑在路线的模型挂钩中用图标装饰人物,利用 Ember 处理异步模型解析的能力,方法如下:

model: function() {
  return this.store.find('person') .
    then(function(people) {
      return Ember.RSVP.Promise.all(people.map(getIcon)) .
        then(function(icons) {
          people.forEach(function(person, i) {
            person.set('icon') = icons[i];
          });
          return people;
        })
      ;
    })
  ;
}

其中 getIcon 类似于

function getIcon(person) {
  return new Ember.RSVP.Promise(function(resolve, reject) {
    $.ajax('http://icon-maker.com?' + person.get('name'), resolve);
  });
}

或者,如果它更干净,您可以将图标内容分解为 afterModel 挂钩:

model: function() { return this.store.find('person'); },

afterModel: function(model) {
  return Ember.RSVP.Promise.all(model.map(getIcon)) .
    then(function(icons) {
      model.forEach(function(person, i) {
        person.set('icon') = icons[i];
      });
    })
  ;
}

现在 Ember 将等待整个 promise 解决,包括获取人员及其图标并将图标贴在人员身上,然后再继续。

HTH.

因为我几乎花了一整天的时间来解决类似的问题,所以这里是我的解决方案。

因为 Ember 出于某种原因不允许您 运行 控制器直接从模板运行(这很荒谬,并且以一些非常愚蠢的方式束缚了您的手,我不不知道到底是谁认为这是个好主意...)对我来说最有意义的事情是创建一个通用的自定义助手,它允许您从模板中 运行 函数 :) 这里的问题是您应该始终将当前范围("this" 变量)传递给该助手。

所以助手可能是这样的:

export default Ember.Helper.helper(function([scope, fn]) {
    let args = arguments[0].slice(2);
    let res = fn.apply(scope, args);
    return res;
});

然后,您可以在您的控制器中创建一个您想要 运行 的函数,例如:

testFn: function(element){
    return element.get('name');
}

然后在您的模板中使用自定义助手调用它:

{{#each items as |element|}}
    {{{custom-helper this testFn element}}}
{{/each}}

帮助程序的前两个参数应始终是 "this" 和您想要 运行 的函数名称,然后您可以根据需要传递任意数量的额外参数。


编辑:无论如何,每次当你认为你需要这样做的时候,你应该想想是否创建一个新的组件会更好(90% 的情况都是这样)