Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

ember.js

Let's say I have a template which iterates over a collection of items, and I want to call a function with each item which is specific to the controller, and not a model-level concern:

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

I'd like to define findIconFor in the controller, because this is something specific to this particular view.

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

But that doesn't work. The template fails to compile. Parse error: Expecting 'STRING', 'NUMBER', 'ID', 'DATA', got 'INVALID'

What is the "ember way" to do this?

like image 694
Brian Genisio Avatar asked Apr 09 '15 16:04

Brian Genisio


2 Answers

I'd use a computed property in the controller:

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

Now you could get the icon from {{person.icon}} and the name from {{person.person.name}}. You might want to improve on that (and the code is untested), but that's the general idea.

like image 160
jnfingerle Avatar answered Sep 20 '22 05:09

jnfingerle


As i spent almost entire day on a similar problem here is my solution.

Because Ember for some reason just doesn't allow you to run a controller functions directly from the template (which is ridiculous and ties your hands in some very stupid ways and i don't know who on earth decided this is a good idea ...) the thing that makes most sense to me is to create an universal custom helper, that allows you to run functions from the template :) The catch here is that you should always pass the current scope (the "this" variable) to that helper.

So the helper could be something like this:

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

Then, you can make a function inside your controller, that you want to run, for example:

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

and then in your template you just call it with the custom helper:

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

The first two arguments to the helper should always be "this" and the name of the function, that you want to run, and then you can pass as many extra arguments as you wish.


Edit: Anyway, every time when you think you need to do this, you should think if it will not be better to create a new component instead (it will be in 90% of the cases)

like image 45
Arntor Avatar answered Sep 21 '22 05:09

Arntor