The Goal

Today, I wanted to play with the GitHub users page in the Aurelia Skeleton Navigation project. I wanted to accomplish something simple: allow the user to select a subset of the users returned by GitHub and display avatar cards only for this subset.

The Plan

My original plan for doing this was simple enough. I would use a repeater to create a list of checkboxes bound to a selected property on the user. I would then create a computed property selectedUsers. selectedUsers would simply use Lodash to filter the users array and return the results as an array.

Why the Plan Failed

This is a naive plan. Computed properties are dirty checked in Aurelia. I am also creating a new array every time selectedUsers is dirty checked. That's bad©. The other problem is that this plan didn't work as expected. When I would select a user, the repeater for avatar cards would repeadly show that user (or the selected users). This would keep going until I deselected all of the users. I'm not sure why this was happening, but I have asked those who know about these sort of things (other Aurelia Core Team members) and will update this post when they explain this to me. Update: it's a bug in the repeater

Plan B, The Right Way

So how should I handle this? I need to create a function on the parent view-model. This function will be called whenever the checked state of a checkbox changes. The function will take the user represented by that checkbox as a parameter and check the selected property for the user. Based on this value, the function will either add or remove this user from an array property on the view-model. No more dirty checking, no more Lodash!

Let's have a look at the final code:

users.html

<template>
  <require from="blur-image"></require>

  <section class="au-animate">
      <h2>${heading}</h2>
      <div class="row">
          <div class="col-md-4">
            <div class="form-group" repeat.for="user of users">
              <label>
                ${user.login}
                <input type="checkbox" checked.bind="user.selected" change.delegate="$parent.userSelected(user)" />
              </label>
            </div>
          </div>
          <div class="col-md-6">
            <div class="col-sm-6 col-md-3 card-container au-animate" repeat.for="user of selectedUsers">
                <div class="card">
                    <canvas class="header-bg" width="250" height="70" blur-image.bind="image"></canvas>
                    <div class="avatar">
                        <img src.bind="user.avatar_url" crossorigin ref="image"/>
                    </div>
                    <div class="content">
                        <p class="name">${user.login}</p>
                        <p><a target="_blank" class="btn btn-default" href.bind="user.html_url">Contact</a></p>
                    </div>
                </div>
            </div>
          </div>
      </div>
  </section>
</template>

As you can see, I use change.delegate="$parent.userSelected(user)" as the lynchpin of this exercise. change.delegate tells Aurelia to call the userSelected method of the parent view-model and pass the user object (from the repeater) when the change event fires. Using delegate tells Aurelia to utilize event delegation for this. This means that new event handlers aren't added to each input element. The event is allowed to bubble to a single event handler created by Aurelia and is thus more performant.
users.js

...
  userSelected(user) {
    if(user.selected === true) {
      this.selectedUsers.push(user);
    }
    else {
      let index = this.selectedUsers.indexOf(user);

      if( index > -1) {
        this.selectedUsers.splice(index, 1);
      }
    }
  }
  ...

In the userSelected function, I simply check if the selected property is true and then either add or remove this user from the selectedUsers array. Aurelia will handle updating the UI as users are added and removed from the selectedUsers property.

Conclusion

With a bit of simple error handling and some smart usage of Aurelia's templating and binding engine, I was able to create a UI to filter an array of GitHub users based on user interaction. Hopefully this sample will help you as you write awesome applications using Aurelia!