Как вызвать функцию контроллера из шаблона в 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" для этого?

Ответ 1

Я бы использовал вычисляемое свойство в контроллере:

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

Теперь вы можете получить значок из {{person.icon}} и имя из {{person.person.name}}. Возможно, вам захочется улучшить это (и код не проверен), но что общая идея.

Ответ 2

Как я провел почти целый день по аналогичной проблеме, это мое решение.

Потому что 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% случаев)

Ответ 3

Если значок - это что-то, связанное с человеком, то, поскольку человек представлен моделью, лучше всего реализовать его как вычисленное свойство на модели человека. Каково ваше намерение попытаться поместить его в контроллер?

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

Тогда, предполагая, что people является массивом person:

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

Альтернатива, предоставляемая работами @jnfingerle (я предполагаю, что вы выяснили, что он предлагает вам цикл за iconPeople), но для создания нового массива, содержащего объекты, кажется, нужно много дополнительной работы. Значок зависит от того, что известно только контроллеру? Если нет, как я уже сказал, зачем логике его вычислять в контроллере?

Где поставить вещи - это вопрос философии и предпочтения. Некоторым людям нравятся модели с голыми костями, которые содержат не что иное, как поля, спускающиеся с сервера; другие люди вычисляют состояние и промежуточные результаты в модели. Некоторые люди вкладывают много вещей в контроллеры, тогда как другие предпочитают легкие контроллеры с большей логикой в ​​ "услугах". Лично я нахожусь на стороне более тяжелых моделей, более легких контроллеров и сервисов. Я не утверждаю, что бизнес-логика, или тяжелые преобразования данных, или подготовка к просмотру должны идти в модели, конечно. Но помните, что модель представляет собой объект. Если есть какой-то интересный объект для объекта, независимо от того, сходит он с сервера или каким-то образом вычисляется, для меня это имеет смысл сделать это в модели.

Помните также, что контроллеры являются частью тесно связанной связи/контроллера/представления. Если есть какая-то конкретная модель, которую вы вычисляете в одном контроллере, вам, возможно, придется добавить ее к другому контроллеру, который, как оказалось, обрабатывает одну и ту же модель. Затем, прежде чем вы это узнаете, вы пишете контроллеры, которые разделяют логику между контроллерами, которые не должны были быть в них в первую очередь.

В любом случае, вы говорите, что ваш значок происходит из "несвязанного хранилища данных". Это звучит асинхронно. Для меня это означает, что, возможно, это подмодель под названием PersonIcon, который является belongsTo в модели person. Вы можете сделать эту работу с правильным сочетанием адаптеров и сериализаторов для этой модели. Самое приятное в этом отношении заключается в том, что вся асинхронность в извлечении значка будет обрабатываться полумагически, либо при создании модели person, либо когда вам действительно нужен значок (если вы скажете async: true).

Но, возможно, вы не используете Ember Data или не хотите идти на все эти неприятности. В этом случае вы можете подумать о том, чтобы украсить человека значком в крючке модели маршрута, используя способность 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];
      });
    })
  ;
}

Теперь Эмбер будет ждать полного обещания, чтобы решить, включая получение людей и их значков и прикрепление значков к людям, прежде чем продолжить.

НТН.