Using ember-animated to re-sort a list

Using ember-animated to re-sort a list

Animations can spice up user interaction but their best use is to help the user understand what's going on on the page, as a result of their action.

I wanted to use animations for the sake of trying out ember-animated, the (not so) new Ember.js library that Edward Faulkner presented at last year's EmberConf, and that triggered a great number of jawdrops in the audience (including mine).

Luckily, I've found a use for an animation that also satisfies the above condition – it enhances comprehension. I set out to animate the list of players when the user changes the sorting criteria via a dropdown.

I had the following code in the template:

<div class="mx-4">
  <select
    name="sort-selector"
    onchange={{action (mut this.sortBy) value="target.value"}}
  >
    <option value="standard">Standard</option>
    <option value="rapid">Rapid</option>
    <option value="blitz">Blitz</option>
    <option value="age">Age</option>
  </select>

  <div class="flex flex-row flex-wrap">
    {{#each this.sortedPlayers as |player|}}
      {{#link-to
        "player"
        player.id
        class="no-underline text-blue hover:text-blue-darker"
      }}
        <PlayerCard @player={{player}} />
      {{/link-to}}
    {{/each}}
  </div>
</div>

Browsing through the docs of ember-animated, I've found {{animated-each}}, which seemed to be exactly what I needed. I also learned that I need to wrap the animated part of my template in a {{animated-container}} so that it "holds a place" for the animated content during animation.

So I added the container, which also replaced the flex container and substituted the plain each with animated-each:

<div class="mx-4">
  <select
    name="sort-selector"
    onchange={{action (mut this.sortBy) value="target.value"}}
  >
    <option value="standard">Standard</option>
    <option value="rapid">Rapid</option>
    <option value="blitz">Blitz</option>
    <option value="age">Age</option>
  </select>

  {{#animated-container class="flex flex-row flex-wrap">
    {{#animated-each this.sortedPlayers key="id" use=transition as |player|}}
      {{#link-to
        "player"
        player.id
        class="no-underline text-blue hover:text-blue-darker"
      }}
        <PlayerCard @player={{player}} />
      {{/link-to}}
    {{/animated-each}}
  {{/animated-container}}
</div>

Lastly, I implemented the motion that gets passed as transition to animated-each. Not being an animation guru, I luckily found an example in the docs that I could simplify.

As the context of the above template is a controller, I added the transition there:

import Controller from '@ember/controller';
import move from 'ember-animated/motions/move';

export default class IndexController extends Controller {
  // (...)
  // eslint-disable-next-line require-yield
  * transition({ /* insertedSprites ,*/ keptSprites/*, removedSprites */ }) {
    keptSprites.forEach(sprite => move(sprite, { duration: 500 }));
  }
}

The transition method is a generator function and that's how you define one with the ES6 class syntax :o .

It gets passed the sprites that get removed (removedSprites), added (insertedSprites), and the ones that remain (keptSprites). In my case, the chess players are only re-sorted so none of them are removed and new ones don't appear either, so I only needed keptSprites. They just move to their new location in the list in half a second.

To my surprise (and satisfaction), that's all I needed:

The reason I think this also improves UX is that you can focus on an individual player and see where he ends up when you change the sorting criterion. For example, you can see Magnus doesn't move one bit except when you sort by age :) (I'm not a Magnus fan but I have to admit his greatness.)