5 Cool Ember Functions

NOTE: As I’ve found too many interesting methods, this will be a two parter, with five more methods to come.

Tomster wearing sunglasses

One of the biggest challenges of learning Ember (or any framework really) is familiarising yourself with all the tools it provides. Sometimes I’ve needed a tool and not known that there’s a specific function baked in that does what I need. Of course there are great resources out there like Stack Overflow and the excellent Ember community Slack channel, but even then sometimes it’s tricky to find what you’re looking for.

I’ve recently challenged myself to read through the Ember API documentation in its entirety. It’s rather long, and a little dry sometimes, but the best part so far has been discovering all sorts of random and neat functions baked into Ember that I didn’t know about. Today I’m going to share 5 of my favourites.

I should start by noting that while I’ve labeled this post as ‘5 Cool Ember Functions’, most of the functions I’m looking at are actually methods - that is, they’re functions stored as the properties of different objects in Ember.

1. loc()

Documentation

This is a method of the Ember.String namespace. Most of the methods in this namespace are concerned with formatting (e.g, changing a string from camelCasing to kebab-casing). The loc() method, however formats the passed string, but first checks to see if it is a key in the Ember.STRING hash. One important use for this is in localisation (or, you know, localization). Take the following example:

1
2
3
4
5
6
7
Ember.STRINGS = {
    '_color': 'colour',
    '_president': 'Prime Minister'
};

Ember.String.loc("_color");  // 'color';
Ember.String.loc("_president");  // 'Prime Minister';

The loc() method can be called as a helper in a template too. Given the above bit of code, you could have a template like this:

<h1>
  {{loc '_president'}}
</h1>

…would be translated to:

<h1>
  Prime Minister
</h1>

2. invoke()

Documentation

This method is part of Ember’s Enumerable mixin, which is inherited by the Ember.Array class, amongst others. In short, it invokes a named function on every element in the collection it is called upon (like an array), with the ability to take additional arguments. One neat use of it is for simplifying foreach iterations where a single function is called upon each element in the collection, like this:

1
2
3
sampleArray.forEach(function(item) {
  item.sampleMethod(argument);
});

Instead, the invoke function can be used to provide a simpler piece of code, like this:

1
sampleArray.invoke('sampleMetod', argument);

Much simpler!

3. mapBy()

Documentation

If you’ve used JavaScript (or, in fact, Ruby), you’re most likely familiar with the Array.prototype.map() function, which allows you to call a function on each item in an enumerable and return a new array with the resulting values. The mapBy() method is similar to this function, but it used when your array is composed of enumerables. It extracts the values of a specified property from those arrays and returns their value as a new array. Take the following example:

1
2
3
4
5
6
7
8
9
10
11
12
var superman = Ember.Object.create({
  superpower: "Invicibility"
});

var batman = Ember.Object.create({
  superpower: "Money"
});

var superheroes = [superman, batman]

superheroes.mapBy('superpower');
//=>["Invicibility", "Money"]

4. sendAction()

Documentation

This method belongs to the Ember.Component class, and calls an action that has been passed into a component from the corresponding controller or route handler. It allows you to translate events that happen within a component (like a click, hover, or scroll) into a specific action, while also passing alongs parameters obtained within the component.

For example, I recently put together a recipe book app that demos HTML5 drag and drop in Ember. As part of the demo, I built a draggable-dropzone component, where items could be dragged over and released, in this case associating an ingredient with a recipe. The JavaScript for this component is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Ember from 'ember';

var { set } = Ember;

export default Ember.Component.extend({
  classNames: ['draggableDropzone'],
  classNameBindings: ['dragClass'],
  dragClass: 'deactivated',

  dragLeave(event) {
    event.preventDefault();
    set(this, 'dragClass', 'deactivated');
  },

  dragOver(event) {
    event.preventDefault();
    set(this, 'dragClass', 'activated');
  },

  drop(event) {
    var data = event.dataTransfer.getData('text/data');
    this.sendAction('dropped', data, this.get('recipeId'));

    set(this, 'dragClass','deactivated');
  }
});

When the drop function is triggered, by dragging an item and dropping it over the component, it assigns the data from the item being dragged (in this case the ID of an ingredient) to a data variable. It then invokes the sendAction() method, which triggers the dropped method, passing both the data variable, and the ID of the recipe currently being shown in the template.

I’ve included this component into the app/templates/recipes/recipe.hbs template using the following code:

{{#draggable-dropzone dropped="addIngredient" recipeId=model.recipe.id}}
...some html
{{/draggable-dropzone}}

You can see here that the dropped method, passed into the component, corresponds to the addIngredient action in the app/routes/recipes/recipe.js route, which looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Ember from 'ember';

export default Ember.Route.extend({
  model: function(params){
    return Ember.RSVP.hash({
      recipe: this.store.findRecord('recipe', params.recipe_id),
      ingredients: this.store.findAll('ingredient')
    });
  },

  actions: {
    addIngredient(ingredientId, recipeId){
      let recipe = this.get('store').peekRecord('recipe', recipeId);
      let ingredient = this.get('store').peekRecord('ingredient', ingredientId);
      recipe.get('ingredients').pushObject(ingredient);
      recipe.save();
    },
    removeIngredient(ingredientId, recipeId){
      let recipe = this.get('store').peekRecord('recipe', recipeId);
      let ingredient = this.get('store').peekRecord('ingredient', ingredientId);
      recipe.get('ingredients').removeObject(ingredient);
      recipe.save();
    }
  }
});

So, you can see that by using the sendAction() method in the component, we’re able to invoke actions from the route or controller corresponding to the template the component is rendered in. In this case, the addIngredient has been triggered, associating the ingredient and recipe together and persisting this information to the database.

5. RSVP.hash()

Documentation

This method belongs to Ember’s RSVP class, and takes an object/hash composed of multiple promises. It returns a promise when all of these promises have been fulfilled, consisting of a hash whose keys mirror those of the original object/hash passed into the method.

If that seems unclear, I found a really great use for this method recently - scenarios where you want to specify multiple models on a single route. Many times, a single route has a single model, like this:

1
2
3
4
5
6
7
import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    return this.get('store').query('modelName');
  }
});

But what if you want to have access to multiple models on a single route? You can, with RSVP.hash(), like this:

1
2
3
4
5
6
7
8
9
10
import Ember from 'ember';

export default Ember.Route.extend({
  model: function(params){
    return Ember.RSVP.hash({
      firstModel: this.store.findRecord('firstModel', params.first_model_id),
      secondModels: this.store.findAll('secondModel')
    });
  },
});

The method waits for both the promises (in this case queries to the database) to be fulfilled, and then returns an object with two keys: firstModel and secondModel, whose values correspond to the data retrieved from the database.

Conclusions

That’s just a taste of some of the interesting methods I’ve come across in Ember so far. As mentioned at the start of this post, I’m going to create a followup showing five more interesting methods, so stay tuned!