I'm all about Ember.js recently

Complex Component Design in Ember - Part 4 - Use the Hash Helper

This is the fourth and final part of my Complex Component Design series. Here are the preceding posts:

You can find the code for this post on Github.


After our last refactoring, the ember-cli-autocomplete component no longer uses observers. However, the list of parameters the outermost, container component, auto-complete returns is now unwieldily long:

<!-- tests/dummy/templates/index.hbs -->
{{#auto-complete
          on-select=(action "selectArtist")
          on-input=(action "filterArtists")
          options=matchingArtists
          displayProperty="name"
          class="autocomplete-container" as |isDropdownOpen inputValue options
                                             focusedIndex selectedIndex
                                             toggleDropdown onSelect onInput|}}

  (...)
{{/auto-complete}}

Not only does that look clumsy, it also makes refactoring more difficult and one always constantly have to flip between the component’s template (where params are yielded from) and the template where the component is used to see if the position of values match. So how can improve this?

Components as functions

To understand several concepts about components, consider them functions. Putting aside the fact that they can also emit DOM elements, you call them with a list of arguments, usually, though not exclusively, a collection of key-value pairs. The component then does some internal stuff and returns a value from its template via the yield keyword.

Our current case is another instance when treating them as functions can help us find the solution. Ask yourself: what would you do if the return value of a function you wrote grew to a long list of arguments? You would convert the return value to a key-value collection, such as a hash, wouldn’t you?

Well, in Ember’s component land, we can do this by using the hash helper, introduced in Ember 2.3. It takes a list of key-value pairs at invocation time and outputs an object (a hash) with them:

{{#with (hash firstName='Mike' lastName='McCready' instrument='guitar') as |musician|}}
  Hello, I'm {{musician.firstName}} {{musician.lastName}} and I play the {{musician.instrument}}.
{{/with}}

We can use the hash helper to bring some sanity to the return value of auto-complete parameters. It currently looks like this:

<!-- addon/templates/components/auto-complete.hbs -->
{{yield isDropdownOpen
        inputValue
        options
        focusedIndex
        selectedIndex
        (action "toggleDropdown")
        (action "selectOption")
        (action "inputDidChange")}}

So we introduce the hash helper to get the following:

<!-- addon/templates/components/auto-complete.hbs -->
{{yield (hash
    isOpen=isDropdownOpen
    inputValue=inputValue
    options=options
    focusedIndex=focusedIndex
    selectedIndex=selectedIndex
    toggleDropdown=(action "toggleDropdown")
    onSelect=(action "selectItem")
    onInput=(action "inputDidChange"))}}

Modifying call sites

Now that the component’s return value has changed, we should not forget to modify the callers, the downstream components that use that value:

<!-- tests/dummy/app/templates/index.hbs -->
{{#auto-complete
      on-select=(action "selectArtist")
      on-input=(action "filterArtists")
      items=matchingArtists
      displayProperty="name"
      class="autocomplete-container" as |params|}}
  <div class="input-group">
    {{auto-complete-input
        value=params.inputValue
        on-change=params.onInput
        type="text"
        class="combobox input-large form-control"
        placeholder="Select an artist"}}
    {{#auto-complete-list
        isVisible=params.isOpen
        class="typeahead typeahead-long dropdown-menu"}}
      {{#each params.options as |option|}}
        {{#auto-complete-option
            index=option.index
            on-click=params.onSelect
            isFocused=(eq params.focusedIndex option.index)
            isSelected=(eq params.selectedIndex option.index)}}
          <a href="#">{{option.value}}</a>
        {{/auto-complete-option}}
      {{else}}
        <li><a href="#">No results.</a></li>
      {{/each}}
    {{/auto-complete-list}}
    {{#auto-complete-dropdown-toggle on-click=params.toggleDropdown class="input-group-addon dropdown-toggle"}}
      <span class="caret"></span>
    {{/auto-complete-dropdown-toggle}}
  </div>
{{/auto-complete}}

Instead of the long list of parameters, auto-complete now yields a single hash parameter (called params above), whose keys are used in the child components (params.isOpen, params.options, etc.)

Polyfill it

Since we want our component to be usable not only in Ember >=2.3 applications, where the hash helper is built in, we should add the ember-hash-helper-polyfill, which makes the hash helper available in earlier Ember versions, as a dependency of the addon:

1
2
3
4
5
6
7
8
9
// package.json
{
  "name": "ember-cli-autocomplete",
  "version": "0.0.0",
  "dependencies": {
    (...)
    "ember-hash-helper-polyfill": "0.1.0"
  },
}

Wrapping up

That wraps up my Complex Component Design in Ember.js series. Our component improved by each post and I think we now have a pretty flexible and thus reusable component. The main purpose of the series, however, is education, so I hope that I was able to transfer some of the knowledge I’ve acquired by building components.

If you would like to read the whole series as a pdf, just give my your email address below and I’m sending it to you.

Rock and Roll With Ember.js 2.5 Is Released

I published a new version of the Rock and Roll Ember.js book, and the related application. It now runs on Ember, Ember Data and Ember CLI ~2.5.0.

More importantly, I made other improvements that serve to improve clarity and reduce the number of new things the reader has to absorb at each step, which I think is hugely important for an efficient, non-frustrating learning process.

  1. The biggest change (and simplification) is that I no longer sort the songs from the get-go. To do so, I needed to use the SortableMixin and later, when that was gone, an ArrayProxy. This resulted in other simplifications, like not having to use (and maintain) a jsbin for that code snippet that used the “global” Ember application building style and iterated on the magical sortedContent property.

  2. I also improved the flow of the Components chapter, rearranged some sections, explained a few things that help comprehension and moved a few things that only add to the learning burden.

  3. I created an Appendix, called “Encore” to further the rock analogy. I felt (and got matching feedback) that on some occasions there were too many “sidebar” explanations (called “Backstage” sections in the book), that either weren’t important enough to warrant holding up the flow of explanation or lacked context. I moved these sections into the Encore where interested readers can learn about these topics when they see fit.

  4. Last, but not least, I went through the book and built the application from scratch to see that everything still works. I also applied git tags at the end of each chapter so that readers of the middle- and high-tier packages can skip to each chapter in the code in a very simple way, using git checkout.

(There were some other changes, the whole list of which you can see here.)

This is the most significant update since I published the Ember 2 version of the book last October and I believe following the book (and the building of the app) became even easier.

If this piqued your interest, you can download a sample chapter below.

Complex Component Design in Ember - Part 3 - Replace the Observer

This is part 3 of my Complex Component Design series. Here are the preceding posts:

You can find the code for this post on Github.


In the last post, we refactored towards a more reactive component and got pretty far. However, we established that we’d still have to remove the observer that was also causing a weird bug:

JPJ is too good to be replaced

Event origin and data owner components are different

The reason we introduced an observer was that we wanted to trigger an action when one of the options was selected via cycling through them and hitting the return key on the focused option. Since the necessary data for that event was contained in the auto-complete-option component but the source of the keyboard event was the auto-complete component, we couldn’t simply trigger the action with the right data from the event source.

We fixed this by using an observer so that even though the event that should trigger the action was fired “upstream”, in the auto-complete component, we could react to this change “downstream”, in the appropriate auto-complete-option whose isSelected property became true:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// addon/components/auto-complete-option.js
import Ember from 'ember';

export default Ember.Component.extend({
  (...)

  didBecomeSelected: Ember.observer('isSelected', function() {
    let isSelected = this.get('isSelected');
    if (isSelected) {
      this._selectItem();
    }
  }),

   _selectItem() {
    let item = this.get('item');
    this.get('on-click')(item, this.get('label'));
  }
});

Our analysis of the cause already hints at the solution. We could move the knowledge of which option is selected up to the auto-complete component and then, when the user hits the return key to select the focused option, trigger the action with the data that we have at our disposal.

Centralized power in auto-complete

Changes in components

We will maintain the selected option in auto-complete and trigger the selectItem action when one of them is selected via a return key event (I skipped the code snippet that calls selectOption for return):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// addon/components/auto-complete.js
import Ember from 'ember';

export default Ember.Component.extend({
  (...)
  selectOption: function(event) {
    event.preventDefault();
    const focusedIndex = this.get('focusedIndex');
    if (Ember.isPresent(focusedIndex)) {
      this.set('selectedIndex', focusedIndex);
      this.send('selectOption', this.get('selectedOption'));
    }
    this.set('isDropdownOpen', false);
  },

  selectedOption: Ember.computed('selectedIndex', 'options.[]', function() {
    return this.get('options').objectAt(this.get('selectedIndex'));
  }),
});

On line 11, we call the selectOption action (renamed from selectItem) with the (new) selected option. selectedOption is simply the option that has the selectedIndex.

Independently of the current selectOption refactor, let’s fix a nasty bug by making sure to reset the focusedIndex when the input changes:

1
2
3
4
5
6
7
8
9
10
11
12
13
// addon/components/auto-complete.js
import Ember from 'ember';

export default Ember.Component.extend({
  (...)
  actions: {
    inputDidChange(value) {
      this.get('on-input')(value);
      this.set('focusedIndex', null);
      (...)
    }
  }
});

Next, let’s look at how the selectOption action needs to change:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// addon/components/auto-complete.js
import Ember from 'ember';

export default Ember.Component.extend({
  (...)
  _displayForOption(option) {
    const displayProperty = this.get('displayProperty');
    return option.get(displayProperty);
  },

  actions: {
    selectOption(option) {
      let inputValue = this._displayForOption(option);
      this.get('on-select')(option);
      this.set('isDropdownOpen', false);
      this.set('inputValue', inputValue);
    },
    (...)
  }
});

One of the things that has changed is that it now only receives one argument, option as the label of the option can now be computed internally, from within the component.

That means that the label now does not need to be passed to the auto-complete-option components and that its action that gets triggered when the user clicks on it needs to be adjusted:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// addon/components/auto-complete-option.js
import Ember from 'ember';

export default Ember.Component.extend({
  tagName: 'li',
  classNames: 'ember-autocomplete-option',
  classNameBindings: Ember.String.w('isSelected:active isFocused:focused'),

  item: null,
  'on-click': null,
  isFocused: false,
  isSelected: false,

  click() {
    this.get('on-click')(this.get('item'));
  }
});

You can see I removed the observer and that I only send the item (not the label, see the very first code example) in the action handler to comply with the new API of the selectOption action.

Changes in templates

Let’s see how the templates need to change to accommodate that change.

First of all, the template of the auto-complete component needs to yield the options to be consumed downstream. Let’s also not forget to rename selectItem to selectOption:

<!-- addon/templates/components/auto-complete.hbs -->
{{yield isDropdownOpen
        inputValue
        options
        focusedIndex
        selectedIndex
        (action "toggleDropdown")
        (action "selectOption")
        (action "inputDidChange")}}

Then, the each loop should iterate through options, and not through matchingArtists as before:

<!-- tests/dummy/app/templates/index.hbs -->
{{#auto-complete
      on-select=(action "selectArtist")
      on-input=(action "filterArtists")
      options=matchingArtists
      displayProperty="name"
      class="autocomplete-container" as |isDropdownOpen inputValue options
                                         focusedIndex selectedIndex
                                         toggleDropdown onSelect onInput|}}
  <div class="input-group">
    {{auto-complete-input
        value=inputValue
        on-change=onInput
        type="text"
        class="combobox input-large form-control"
        placeholder="Select an artist"}}
    {{#auto-complete-list
        isVisible=isDropdownOpen
        class="typeahead typeahead-long dropdown-menu"}}
      {{#each options as |option index|}}
        {{#auto-complete-option
            item=option
            on-click=onSelect
            isFocused=(eq focusedIndex index)
            isSelected=(eq selectedIndex index)}}
          <a href="#">{{option.name}}</a>
        {{/auto-complete-option}}
      {{else}}
        <li><a href="#">No results.</a></li>
      {{/each}}
    {{/auto-complete-list}}
    {{#auto-complete-dropdown-toggle on-click=toggleDropdown class="input-group-addon dropdown-toggle"}}
      <span class="caret"></span>
    {{/auto-complete-dropdown-toggle}}
  </div>
{{/auto-complete}}

The bug at the beginning of the post is now gone:

JPG too-good-to-go bug fixed

In the next episode…

We now have a working, state-of-the-art component design with no coupling between the sub-components and no observers. One thing that is not ideal, though, is the number of parameters the auto-complete components yields (see last code snippet).

Just as you wouldn’t have a method with 7 or 8 positional parameters, you don’t want a component that yields that many properties matched by position. So in the next installment of this series, we’ll use the hash helper to transform that long list into keyed parameters.

Rock and Roll With Ember.js - Now on 2.4 and With Code Diffs

I have just sent an updated version of Rock and Roll with Ember.js to my readers. The app now runs on Ember 2.4.3.

The biggest change in this release is that I now leverage code diffs in code snippets, where this makes understanding changes easier. It looks like this in the pdf version:

Colored code diffs

Since the last release was more than two months ago and I constantly improve things, there is a whole slew of other changes that you can see here.

If you are not yet a reader yet and want to have an always up-to-date Ember guide book, sign up below to get a sample chapter:

Rock and Roll With Ember.js Demo - a Public Ember.js Example Project

I have a book called Rock and Roll with Ember.js that has an accompanying application we develop throughout the book. I also maintain a demo version of the same app which has been open-source since its inception. However, that demo app has not received updates for a while now so I decided to do something about this and spent some time this week on making it a state-of-the-art Ember 2 application.

Here are the main developments I have made:

  • Upgraded it to use the latest Ember, Ember Data and Ember CLI versions, 2.3.0.
  • Used ember-cli-mirage to seed the app with data and handle “backend” requests. (Look, ma’, no backend!) ember-cli-mirage is fantastic addon that lets you set up route handlers, fixtures, factories and many more to manage your seed data and mock your server responses, both in development and tests. This was the first time I seriously used it and I have grown to like it a ton! The author, Sam Selikoff, helped out tremendously and had an amazing turnaround on a few questions/issues. I used the latest beta version, 0.2.0-beta.7, which you should definitely check out and give feedback to Sam.
  • Made it a modern, idiomatic Ember app. It’s not just Ember, but also Javascript that evolves at a neck-breaking space (although to a lesser extent). I used the not-at-all elementary ember-watson to modernize the Ember app and applied a few manual tweaks for some of the Javascript parts, like using destructuring and let and const instead of var.
  • Deployed it to be publicly accessible. Leveraging the most excellent PageFront, the app is now deployed to their platform. You can see it in action at https://rarwe-demo.pagefrontapp.com. This was not even a task, I only needed to issue two commands, one to install the add-on and one to deploy it.

The source lives on Github, at balinterdi/rarwe-demo.

As Mirage can also be used in production (although it’s probably not common to do that), you can check out the “production” app, with the same seed data I used in development, and play around with it.

As I mentioned in the introduction, the full version of the app is developed chapter by chapter in the Rock and Roll with Ember book. You can download a sample chapter below:

How to Make an Ember Module Resolve as Another One

I wanted to write another short, and hopefully useful, post just as I did recently for binding the style attribute.

No configuration is simpler than no configuration

About a month ago I was working to add authorization to the Rock and Roll with Ember application. I used my favorite addon, Torii, to help with that and opted to do the authorization via the google-oauth2-bearer provider. To restore the session, Torii looks up the application (Torii) adapter, but the session initialization and closing code used the google-oauth2-bearer adapter. So I had two separate files, which I was not happy about and I did not want to merge everything into the application adapter, as it does not give a hint about its contents then.

My idea was to make it possible to use another adapter to restore the session from, via a configuration option. Matthew Beale hinted at a solution that removes the need for a configuration option and since I haven’t seen this before, I want to share it with you.

Import from target module, then reexport

The Ember resolver is the piece that maps qualified full names (like route:blog or controller:bands) to module names.

In my case, Torii makes the resolver look up torii-adapter:application to fetch the session from and I wanted this to be resolved to torii-adapter:google-oauth2-bearer. In the Ember CLI project, that is equivalent of having the app/torii-adapters/application.js file export what is exported by app/torii-adapters/google-oauth2-bearer.js.

When phrased like this, the solution is near and I am somewhat embarrassed it took me a few attempts to arrive at this.

So the solution is to import in app/torii-adapters/application.js what app/torii-adapters/google-oauth2-bearer.js exports and then reexport it:

1
2
3
4
// app/torii-adapters/application.js
import GoogleOAuth2BearerAdapter from './google-oauth2-bearer';

export default GoogleOAuth2BearerAdapter;
1
2
3
4
// app/torii-adapters/google-oauth2-bearer.js
export default Ember.Object.extend({
  (...)
});

Voila, we have “tricked” the resolver without adding any configuration (and thus complexity) to the addon.

Complex Components in Ember.js - Part 2 - Towards a More Reactive Component

This is part 2 of my Complex Component Design series. Here are the posts in the series:


In the previous part of this series, the implementation of the main user flows were explained in detail. I ended the post by saying that I was not content with the implementation for several reasons, the most crucial of which was that parent components needed to be passed down to children, so that children can register themselves with their parent. That, in turn, allowed parents to reach their children and call methods on them directly instead of using events, actions and data bindings for communication. In this post, we’ll see how to get rid of these and replace them with more reactive solutions.

Remove the need for direct access to the input

Currently, the autocomplete component (the parent) yields itself to its children. auto-complete-input binds its own autocomplete attribute to it so that it can register itself with its parent when inserted:

<!-- tests/dummy/app/templates/index.hbs -->
{{#auto-complete
      on-select=(action "selectArtist")
      on-input=(action "filterArtists")
      class="autocomplete-container" as
        |autocomplete isDropdownOpen inputValue
         toggleDropdown onSelect onInput|}}
  <div class="input-group">
    {{auto-complete-input
        autocomplete=autocomplete
        value=inputValue
        on-change=onInput
        type="text"
        class="combobox input-large form-control"
        placeholder="Select an artist"}}
    (...)
  </div>
(...)
{{/auto-complete}}
1
2
3
4
5
6
7
8
9
10
11
// addon/components/auto-complete-input.js
import Ember from 'ember';

export default Ember.TextField.extend({
  autocomplete: null,

  registerWithAutocomplete: Ember.on('didInsertElement', function() {
    this.get('autocomplete').registerInput(this);
  }),
  (...)
});

This is needed when the item is autocompleted and the autocompleted segment is pre-selected so that the user can type over it if it’s not the item they had in mind:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// addon/components/auto-complete.js
export default Ember.Component.extend({
  (...)
  actions: {
    inputDidChange(value) {
      (...)
      Ember.run.scheduleOnce('afterRender', this, function() {
        (...)
        const firstOption = this.get('list.firstOption');
        if (firstOption) {
          const autocompletedLabel = firstOption.get('label');
          this.set('focusedOption', firstOption);
          this.get('on-select')(firstOption.get('item'));
          this.set('inputValue', autocompletedLabel);
          Ember.run.next(() => {
            this.get('input.element').setSelectionRange(value.length, autocompletedLabel.length);
          });
        }
      });
    }
  }
});

On the very last line, the component accesses the input directly, to select (and highlight) the portion of the item that was autocompleted. That’s why we need the whole registration process.

Since inputDidChange is triggered from the auto-complete-input component, we could get rid of this direct coupling if there was a way to react to the action’s result in the auto-complete-input itself. That way is called closure actions.

Fire, but don’t forget

As opposed to the fire-and-forget nature of “ordinary” (aka. element) actions, closure actions provide a way to react to the action’s outcome at the source, where the action was fired from.

Since closure actions are functions, they can have return values. If the action triggers an async action, it’s best to return a promise from the upstream handler to which the event source can attach its handler to.

Let’s see how that works in our case.

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
// addon/components/auto-complete.js
export default Ember.Component.extend({
  (...)
  actions: {
    inputDidChange(value) {
      this.get('on-input')(value);
      this.set('isDropdownOpen', true);
      return new Ember.RSVP.Promise((resolve, reject) => {
        (...)
        Ember.run.scheduleOnce('afterRender', this, function() {
          const firstOption = this.get('list.firstOption');
          if (firstOption) {
            const autocompletedLabel = firstOption.get('label');
            this.set('focusedOption', firstOption);
            this.get('on-select')(firstOption.get('item'));
            this.set('inputValue', autocompletedLabel);
            Ember.run.next(() => {
              resolve({ start: value.length, end: autocompletedLabel.length });
            });
          }
        });
      });
    }
  }
});

The code did not change a lot, but now a promise is returned on line 8. It is resolved on 18, where start and end designate the cursor positions of the selection.

The action handler in the auto-complete-input component needs to be modified to set the selection higlight itself:

1
2
3
4
5
6
7
8
9
10
11
// addon/components/auto-complete-input.js
import Ember from 'ember';

export default Ember.TextField.extend({
  valueDidChange: Ember.on('input', function() {
    const value = this.$().val();
    this.get('on-change')(value).then(({ start, end }) => {
      this.get('element').setSelectionRange(start, end);
    });
  })
});

Calling on-change will call the above inputDidChange function. Instead of firing the (element) action and forgetting about it, we now call the (closure) action and then “wait” for the resulting promise to be resolved. Once it does, we set the selection range.

We could now remove all the registration code and the passing down of the autocomplete instance to the input component.

Remove the need for direct access to the list options

There is still another instance of the same. It serves to give access to the autocomplete component to the auto-complete-option, through the auto-complete-list.

<!-- tests/dummy/app/templates/index.hbs -->
{{#auto-complete
      on-select=(action "selectArtist")
      on-input=(action "filterArtists")
      class="autocomplete-container" as |autocomplete isDropdownOpen inputValue
                                         toggleDropdown onSelect onInput|}}
  <div class="input-group">
    {{auto-complete-input
        value=inputValue
        on-change=onInput
        type="text"
        class="combobox input-large form-control"
        placeholder="Select an artist"}}
    {{#auto-complete-list autocomplete=autocomplete isVisible=isDropdownOpen class="typeahead typeahead-long dropdown-menu" as |list|}}
      {{#each matchingArtists as |artist|}}
        {{#auto-complete-option
            id=artist.id
            label=artist.name
            item=artist
            list=list
            on-click=onSelect
            activeId=selectedArtist.id}}
          <a href="#">{{artist.name}}</a>
        {{/auto-complete-option}}
      {{/each}}
    {{/auto-complete-list}}
    (...)
  </div>
{{/auto-complete}}

I am not copying all the registration code here as it’s very boilerplatey. Each option, when inserted into the DOM, registers itself with its list, while the list registers itself with the auto-complete component. The latter has an options property to access the options:

1
2
// addon/components/auto-complete.js
options: Ember.computed.readOnly('list.options')

This access is needed to be able to cycle through the options by using the cursor keys and then select one of them by using the return key. Here is the code that handles keypresses (more precisely, keydowns):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// addon/components/auto-complete.js
export default Ember.Component.extend({
  (...)
  keydownMap: {
    8:  'startBackspacing', // backspace
    13: 'selectOption',  // return
    27: 'closeDropdown', // escape
    38: 'focusPrevious', // up key
    40: 'focusNext', // down key
  },

  handleKeydown: Ember.on('keyDown', function(event) {
    const map = this.get('keydownMap');
    const code = event.keyCode;
    const method = map[code];
    if (method) {
      return this[method](event);
    }
  }),
  (...)
});

This is pretty simple so far. If a key we care about was pressed, we call the appropriate method to handle it. Let’s see how focusing works:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// addon/components/auto-complete.js
export default Ember.Component.extend({
  (...)
  options: Ember.computed.readOnly('list.options'),

  focusPrevious: function(event) {
    event.preventDefault();
    const focused = this.get('focusedOption');
    let index = this.get('options').indexOf(focused);
    if (this.get('isDropdownOpen')) {
      index = index - 1;
    }
    this.focusOptionAtIndex(index);
  },

  focusNext: function(event) {
    event.preventDefault();
    let index = 0;
    const focused = this.get('focusedOption');
    if (focused) {
      index = this.get('options').indexOf(focused);
      if (this.get('isDropdownOpen')) {
        index = index + 1;
      }
    }
    this.focusOptionAtIndex(index);
  },

  focusOptionAtIndex: function(index) {
    const options = this.get('options');
    if (index === -1) {
      index = options.get('length') - 1;
    } else if (index === options.get('length')) {
      index = 0;
    }
    const option = this.get('options').objectAt(index);
    if (!option) {
      return;
    }
    this.focusOption(option);
  },

  focusOption: function(option) {
    const focused = this.get('focusedOption');
    if (focused) {
      focused.blur();
    }
    this.set('focusedOption', option);
    option.focus();
  },
  (...)
});

focusPrevious and focusNext make sure that the focused index is kept within the bounds of the avaiable number of options and then focus the previous (or next) one by calling option.focus() directly (line 49).

There is one more key press concerning related to options, the return key. It should select the currently focused option, if there is one:

1
2
3
4
5
6
7
8
9
10
11
12
13
// addon/components/auto-complete.js
export default Ember.Component.extend({
  (...)
  options: Ember.computed.readOnly('list.options'),
  selectOption: function(event) {
    event.preventDefault();
    const focused = this.get('focusedOption');
    if (focused) {
      this.send('selectItem', focused.get('item'), focused.get('label'));
    }
    this.set('isDropdownOpen', false);
  },
});

This code also leverages the access to the options, indirectly through this.get('focusedOption'). Furthermore, it assumes that each option has an item and label properties. Not stellar.

It won’t be a piece of cake to get rid of direct coupling in all of these, so let’s get to it.

Change the focused option without accessing the options

In the first step, we’ll change the focused option without directly commanding the options to focus/unfocus. We’ll then tackle selecting the focused option.

We can use simple data binding to have the focused option available. By maintaining and yielding a focusedIndex in the “control center”, the autocomplete component, autocomplete-option components can bind to it and know whether they are focused or not.

Here is how the templates need to change:

<!-- addon/templates/components/autocomplete.hbs -->
{{yield isDropdownOpen
        inputValue
        focusedIndex
        selectedIndex
        (action "toggleDropdown")
        (action "selectItem")
        (action "inputDidChange")}}
<!-- tests/dummy/app/templates/index.hbs -->
{{#auto-complete
      on-select=(action "selectArtist")
      on-input=(action "filterArtists")
      options=matchingArtists
      displayProperty="name"
      class="autocomplete-container" as |isDropdownOpen inputValue
                                         focusedIndex selectedIndex
                                         toggleDropdown onSelect onInput|}}
  <div class="input-group">
    {{auto-complete-input
        value=inputValue
        on-change=onInput
        type="text"
        class="combobox input-large form-control"
        placeholder="Select an artist"}}
    {{#auto-complete-list
        isVisible=isDropdownOpen
        class="typeahead typeahead-long dropdown-menu" as |list|}}
      {{#each matchingArtists as |artist index|}}
        {{#auto-complete-option
            label=artist.name
            item=artist
            on-click=onSelect
            isFocused=(eq focusedIndex index)
            isSelected=(eq selectedIndex index)}}
          <a href="#">{{artist.name}}</a>
        {{/auto-complete-option}}
      {{else}}
        <li><a href="#">No results.</a></li>
      {{/each}}
    {{/auto-complete-list}}
    (...)
  </div>
{{/auto-complete}}

Note the new focusedIndex and selectedIndex attributes, yielded by the top-level component that isFocused and isSelected in the auto-complete-option are bound to.

The eq helper comes from ember-truth-helpers and will evaluate to true if its params are equal which is exactly what we want.

The autocomplete component needs to change to manage the new indexes instead of setting its focusedOption and calling option.set directly:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// addon/components/auto-complete.js
export default Ember.Component.extend({
  (...)
  optionsLength: Ember.computed.readOnly('options.length'),
  focusPrevious: function(event) {
    event.preventDefault();
    const currentIndex = this.get('focusedIndex');
    let newIndex;
    if (Ember.isNone(currentIndex)) {
      newIndex = this.get('optionsLength') - 1;
    } else if (currentIndex === 0) {
      newIndex = this.get('optionsLength') - 1;
    } else {
      newIndex = currentIndex - 1;
    }
    this.set('focusedIndex', newIndex);
    this.set('isDropdownOpen', true);
  },

  focusNext: function(event) {
    event.preventDefault();
    const currentIndex = this.get('focusedIndex');
    const lastIndex = this.get('optionsLength') - 1;
    let newIndex;
    if (Ember.isNone(currentIndex)) {
      newIndex = 0;
    } else if (currentIndex === lastIndex) {
      newIndex = 0;
    } else {
      newIndex = currentIndex + 1;
    }
    this.set('focusedIndex', newIndex);
    this.set('isDropdownOpen', true);
  },

  selectOption: function(event) {
    event.preventDefault();
    const focusedIndex = this.get('focusedIndex');
    if (Ember.isPresent(focusedIndex)) {
      this.set('selectedIndex', focusedIndex);
    }
    this.set('isDropdownOpen', false);
  },
});

That is simpler and less intrusive than before. (Setting isDropdown to true has been added as before the option’s focus method did the opening).

What’s missing is for the selected item to be sent to the outer world (in other words, for the selectItem to be triggered). Before, it was done by sending the selectItem action with the focused option’s item and label (see line 9 in the last snippet of the previous section) but we can no longer indulge in accessing the options directly. Consequently, it was replaced by setting the selectedIndex to the focusedIndex (see line 40 above).

The problem now is that selectItem needs to be called with the item and the label (the name of the selected artist to be set as the input’s value) and only the selected auto-complete-option component has that knowledge. So we need to set up a way for the auto-complete-option components to know when they become selected and then call that action. As these components are not the source of the event that lead to an option being selected by key press, we choose to use an observer:

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
27
28
29
30
// addon/components/auto-complete-option.js
import Ember from 'ember';

export default Ember.Component.extend({
  tagName: 'li',
  classNames: 'ember-autocomplete-option',
  classNameBindings: Ember.String.w('isSelected:active isFocused:focused'),

  label: null,
  item: null,
  'on-click': null,
  isFocused: false,
  isSelected: false,

  didClick: Ember.on('click', function() {
    this._selectItem();
  }),

  didBecomeSelected: Ember.observer('isSelected', function() {
    const isSelected = this.get('isSelected');
    if (isSelected) {
      this._selectItem();
    }
  }),

  _selectItem() {
    const item = this.get('item');
    this.get('on-click')(item, this.get('label'));
  }
});

Line 21 and 22 is where the option realizes it has become the selected option, and then calls the corresponding (closure) action on line 28.

We’re done, we got rid of all the direct passing of component instances, registrations and direct property access and method calling. Even though we’re Demeter compliant, there are things that could be improved.

In the next episode…

One of these things is the observer. Observers fell out of favor some time ago, and for a good reason. They can be over-eager and lead to scenarios where it is hard to see what’s going on. To prove my point, let me show you a bug I’ve just accidentally introduced. I call it the “JPJ is too good to be replaced” bug:

JPJ is too good to be replaced

(The code for this series is publicly available on Github here. I’ve tagged where we are now with ccd-part-two.)

So we’re not done yet. In the next post of the series, we’re going to fix that bug by replacing the observer and make other worthy improvements. Stay tuned!

Binding Style Attributes Warning in Ember

One warning Ember might print in your console concerns binding a property to the style attribute of a DOM element, like this:

<div class="progress-bar" style="{{barWidth}}">...</div>
export default Ember.Controller.extend({
  progress: 0,
  barWidth: Ember.computed('progress', {
    return 'width:' + this.get('progress') + '%';
  });
});

Handlebars escapes all html content put in double curlies but it does not do that with CSS, and thus the above makes possible a cross-site scripting attack. That is the reason for the warning and the fix for that is to convert the property (in the above case, barWidth) to a SafeString, which tells Ember that the content is safe to display. You should only do that after you have verified that the content you mark as safe cannot be injected by a malicious user. The guide describes how to do that:

export default Ember.Controller.extend({
  progress: 0,
  barWidth: Ember.computed('progress', {
    return new Ember.Handlebars.SafeString('width:' + this.get('progress') + '%');
  });
});

(Alternatively, you can call Ember.String.htmlSafe with the string you want to mark as safe, to the same effect.)

When I did this conversion in a recent project, though, the warning persisted. After spending a substantial amount of time pouring over the docs and even stepping through the warning stacktrace, I still could not find out what was wrong. What helped (as so many times already) was a good night sleep and taking another look at it in the morning.

Marking the string as safe was done correctly, but when binding it to the style attribute, I used double quotes around it, probably inhibiting Ember from seeing it as a SafeString:

<div class="progress-bar" style="{{barWidth}}">...</div>

So all I had to do to make the warning go away was to remove the quotes:

<div class="progress-bar" style={{barWidth}}>...</div>

I hope this saves you some time if you come across a similar situation in your work.

Rock and Roll With Ember.js Updated to Use Ember & Co 2.3

I have just released a new update to the Rock and Roll Ember.js book, which brings it up to Ember 2.3.0, ED 2.3.2 and Ember CLI 2.3.0-beta.1.

Other improvements made in this release are:

  • Update the PageFront section as it has now become even simpler to deploy apps to PageFront
  • Reset the song creation process when switching bands
  • Use ember-bootstrap and remove the section on manually adding assets to the build
  • Use consistent quoting in snippets and include import Ember from 'ember' wherever needed.
  • Remove a few paragraphs that talk about how things were in the past (like bind-attr)

Happy reading!

Complex Components in Ember.js - Part 1 - Analyzing User Flows

This is Part 1 of the Complex Component Design series. Here are the posts in the series:


In this post I continue the Complex Component Design series I started back in September. I slightly renamed the series title as the original idea was to design and develop the component in the parts of the series but since the component is mostly “done”, I prefer to show how it works and how the different pieces fit together. I think this way of presenting things is still (perhaps equally) valuable and we’ll have a few open issues to work on “together” to further improve the component.

The component I described in the intro post serves to select an item from a list of items, either via a dropdown or by starting to type its name and then selecting it. Here is a very short demo about how that looks in practice:

Selecting an artist

We’ll go through the main UI flows and see how they are implemented via communication of the different layers of the component.

Getting familiar with the component

The template we’ll use (and which the above demo uses) to understand the functioning of the component looks like this:

<!-- tests/dummy/app/templates/index.hbs -->
<div class="form-group">
  <label>Choose an artist</label>
  {{#auto-complete
        on-select=(action "selectArtist")
        on-input=(action "filterArtists")
        class="autocomplete-container" as |autocomplete isDropdownOpen inputValue
                                           toggleDropdown onSelect onInput|}}
    <div class="input-group">
      {{auto-complete-input
          autocomplete=autocomplete
          value=inputValue
          on-change=onInput
          type="text"
          class="combobox input-large form-control"
          placeholder="Select an artist"}}
      {{#auto-complete-list autocomplete=autocomplete isVisible=isDropdownOpen
              class="typeahead typeahead-long dropdown-menu" as |list|}}
        {{#each matchingArtists as |artist|}}
          {{#auto-complete-option
              id=artist.id
              label=artist.name
              item=artist
              list=list
              on-click=onSelect
              activeId=selectedArtist.id}}
            <a href="#">{{artist.name}}</a>
          {{/auto-complete-option}}
        {{else}}
          <li><a href="#">No results.</a></li>
        {{/each}}
      {{/auto-complete-list}}
      {{#auto-complete-dropdown-toggle on-click=toggleDropdown
              class="input-group-addon dropdown-toggle"}}
        <span class="caret"></span>
      {{/auto-complete-dropdown-toggle}}
    </div>
  {{/auto-complete}}
</div>

This might seem somewhat daunting at first but as we grow acquainted with its details, our intimidation will subside.

The top-level component is auto-complete. This is the “command center”, the piece that manages the “global” state of the whole widget, like whether the dropdown is visible and what the current value of the input field is.

You might, with good reason, wonder why these are not handled by the sub-component where it’d feel more appropriate: the current value of the input field by auto-complete-input and the opened/closed state of the dropdown by auto-complete-dropdown-toggle.

The answer is that a change in these states can be triggered from multiple places and that several child components might need to know about them. The dropdown can be closed by the user clicking on one of the items in the dropdown (not on the little arrow of the toggle), while the current text in the input can be modified by inferring the item when the user starts to type (not just by actually typing out the whole text).

Data down, actions up - all the way down (and up)

That slight violation of separation of concerns (or is it at all?) fits perfectly with the most important component communication paradigm: Data down, actions up.

The input, when its value changes, sends an action up to its parent, notifying it of the change. The parent can then react to this, and communicate any data (state) changes via the attribute bindings it has to the input. This is why auto-complete needs to handle, or at least access, state that is used downstream by its sub-components.

The classical way of passing down data (and establishing a binding) from the parent to the child is through block parameters of the parent component. The auto-complete component has quite some:

<!-- tests/dummy/app/templates/index.hbs -->
{{#auto-complete
      on-select=(action "selectArtist")
      on-input=(action "filterArtists")
      class="autocomplete-container" as |autocomplete isDropdownOpen inputValue
                                         toggleDropdown onSelect onInput|}}
  (...)
{{/auto-complete}}

The block parameters are those found between the pipes, after the as keyword. You have to look into the component’s own template to see where they come from:

<!-- addon/templates/components/auto-complete.hbs -->
{{yield this isDropdownOpen inputValue
        (action "toggleDropdown") (action "selectItem") (action "inputDidChange")}}

Parameters are matched by position, so what is yielded in the first position becomes the first block parameter. In this case, we yield the component itself as the first parameter, the aforementioned component states as the 2nd and 3rd and then (closure) actions that will trigger functions in the auto-complete component when called in one of the child components. These serve as “remote controls” (a term used by Miguel Camba in his awesome presentation at EmberCamp) for child components to control their parent.

The way of upward communication from child components is calling these actions when appropriate.

We now have sufficient knowledge to follow the implemention of basic user flows, so let’s get into it.

Understanding UX flows

Manual selection from the dropdown

The most basic thing one can do with the widget is to pop open the list of options.

I discarded the parts that are not relevant to understand this, so we’re left with the following:

<!-- tests/dummy/app/templates/index.hbs -->
<div class="form-group">
  <label>Choose an artist</label>
  {{#auto-complete
        on-select=(action "selectArtist")
        on-input=(action "filterArtists")
        class="autocomplete-container" as |autocomplete isDropdownOpen inputValue
                                           toggleDropdown onSelect onInput|}}
    <div class="input-group">
      {{#auto-complete-list autocomplete=autocomplete isVisible=isDropdownOpen
              class="typeahead typeahead-long dropdown-menu" as |list|}}
        (...)
      {{/auto-complete-list}}
      {{#auto-complete-dropdown-toggle on-click=toggleDropdown
              class="input-group-addon dropdown-toggle"}}
        <span class="caret"></span>
      {{/auto-complete-dropdown-toggle}}
    </div>
  {{/auto-complete}}
</div>

The auto-complete-dropdown-toggle is the component that can be clicked to open or close the list of items. At a glance it seems like its on-click attribute is the action that will be triggered when the user clicks it but let’s see for sure:

1
2
3
4
5
6
7
8
9
10
11
12
13
// addon/components/auto-complete-dropdown-toggle.js
import Ember from 'ember';

export default Ember.Component.extend({
  tagName: 'span',
  classNames: 'ember-autocomplete-toggle',
  'data-dropdown': 'dropdown',
  'on-click': null,

  toggleDropdown: Ember.on('click', function() {
    this.get('on-click')();
  })
});

Indeed, it just calls the action that was passed into it, which is the toggleDropdown action of the topmost auto-complete component:

1
2
3
4
5
6
7
8
9
10
11
// addon/components/auto-complete.js
import Ember from 'ember';

export default Ember.Component.extend({
  (...)
  actions: {
    toggleDropdown() {
      this.toggleProperty('isDropdownOpen');
    },
  }
});

The toggleProperty method flips the value of its parameter, so if it was false it now becomes true. isDropdownOpen is yielded as a block parameter so when it becomes true, auto-complete-list will rerender as one of its attributes, isVisible has changed. That will then open the dropdown:

<!-- tests/dummy/app/templates/index.hbs -->
<div class="form-group">
  <label>Choose an artist</label>
  {{#auto-complete
      (...)
      class="autocomplete-container" as |autocomplete isDropdownOpen inputValue
                                           toggleDropdown onSelect onInput|}}
    <div class="input-group">
      {{#auto-complete-list autocomplete=autocomplete isVisible=isDropdownOpen
              class="typeahead typeahead-long dropdown-menu" as |list|}}
        (...)
      {{/auto-complete-list}}
    </div>
  {{/auto-complete}}
</div>

The same process is triggered when the toggle is clicked again, only this time isDropdownOpen goes back to false and thus the dropdown is closed.

Picking an item

The second feature we’ll look at is more like the second half of the first one: selecting an item by clicking (tapping) on it.

I have again restrained the template to the relevant bits, throwing away the input and the toggle:

<!-- tests/dummy/app/templates/index.hbs -->
<div class="form-group">
  <label>Choose an artist</label>
  {{#auto-complete
        on-select=(action "selectArtist")
        on-input=(action "filterArtists")
        class="autocomplete-container" as |autocomplete isDropdownOpen inputValue
                                           toggleDropdown onSelect onInput|}}
    <div class="input-group">
      (...)
      {{#auto-complete-list autocomplete=autocomplete isVisible=isDropdownOpen
              class="typeahead typeahead-long dropdown-menu" as |list|}}
        {{#each matchingArtists as |artist|}}
          {{#auto-complete-option
              id=artist.id
              label=artist.name
              item=artist
              list=list
              on-click=onSelect
              activeId=selectedArtist.id}}
            <a href="#">{{artist.name}}</a>
          {{/auto-complete-option}}
        {{else}}
          <li><a href="#">No results.</a></li>
        {{/each}}
      {{/auto-complete-list}}
      (...)
    </div>
  {{/auto-complete}}
</div>

When one of the items is clicked, the on-click attribute (which is the onSelect closure action provided by auto-complete) is called in the auto-complete-option component:

1
2
3
4
5
6
7
8
9
// addon/components/auto-complete-option.js
import Ember from 'ember';

export default Ember.Component.extend({
  (...)
  selectOption: Ember.on('click', function() {
    this.get('on-click')(this.get('item'), this.get('label'));
  }),
});

So where is onSelect defined? It is one of the block parameters yielded by auto-complete, more precisely the (action "selectItem") action:

<!-- addon/templates/components/auto-complete.hbs -->
{{yield this isDropdownOpen inputValue
        (action "toggleDropdown") (action "selectItem") (action "inputDidChange")}}

selectItem is quite straightforward:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// addon/components/auto-complete.js
import Ember from 'ember';

export default Ember.Component.extend({
  (...)
  actions: {
    selectItem(item, value) {
      this.get('on-select')(item);
      this.set('isDropdownOpen', false);
      this.set('inputValue', value);
    },
    (...)
  }
});

It first calls the on-select action that was passed into it from the “outside” (the controller), which just sets selectedArtist to the artist object encapsulated in the list item. It then sets the isDropdownOpen flag to false (which, by the mechanism seen in the previous point, closes the list) and sets the text in the input to the item’s label (the artist’s name).

Auto-completing an item

As the final example, let’s see a more complicated use case. When the user starts to type, the items that do not match the typed string will not be shown as options. Also, the first matching item will be auto-completed and selected, and the dropdown will be closed.

No surprises here, the same design principle will be applied as before. Pass down an action that should be called from a child, then change some property in the parent component that trickles down to the child which then rerenders itself because of the changed attribute.

Let’s see the relevants parts of the template:

<!-- tests/dummy/app/templates/index.hbs -->
<div class="form-group">
  <label>Choose an artist</label>
  {{#auto-complete
        on-select=(action "selectArtist")
        on-input=(action "filterArtists")
        class="autocomplete-container" as |autocomplete isDropdownOpen inputValue
                                           toggleDropdown onSelect onInput|}}
    <div class="input-group">
      {{auto-complete-input
          autocomplete=autocomplete
          value=inputValue
          on-change=onInput
          type="text"
          class="combobox input-large form-control"
          placeholder="Select an artist"}}
      {{#auto-complete-list autocomplete=autocomplete isVisible=isDropdownOpen
              class="typeahead typeahead-long dropdown-menu" as |list|}}
        {{#each matchingArtists as |artist|}}
          {{#auto-complete-option
              (...)
          {{/auto-complete-option}}
        {{else}}
          <li><a href="#">No results.</a></li>
        {{/each}}
      {{/auto-complete-list}}
      (...)
    </div>
  {{/auto-complete}}
</div>

We’ll start by the auto-complete-input this time where the input event, triggered by the user’s typing, is handled:

1
2
3
4
5
6
7
8
9
10
// addon/components/auto-complete-input.js
import Ember from 'ember';

export default Ember.TextField.extend({
  (...)
  valueDidChange: Ember.on('input', function() {
    const value = this.$().val();
    this.get('on-change')(value);
  })
});

This is almost the exact copy of calling the on-select action we saw before from auto-complete-option. Here, the on-change function is called that was passed down from the block param of auto-complete.

If we take a look in the template of auto-complete we see it creates a (action 'inputDidChange') closure action and yield that, so that should be the next thing to look at. Here is where most of the stuff happens:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// addon/components/auto-complete.js
import Ember from 'ember';

export default Ember.Component.extend({
  (...)
  actions: {
    inputDidChange(value) {
      this.get('on-input')(value);
      this.set('isDropdownOpen', true);
      const firstOption = this.get('list.firstOption');
      if (firstOption) {
        const autocompletedLabel = firstOption.get('label');
        this.get('on-select')(firstOption.get('item'));
        this.set('inputValue', autocompletedLabel);
        this.get('input.element').setSelectionRange(value.length, autocompletedLabel.length);
      }
    }
  }
});

We first call the on-input action which filters out the artists that do not match the typed prefix. The result of that is that matchingArtists will only contain the artists that do match. The dropdown is then opened to display these items (or an explanatory blurb if none matches). If there is at least one matching item, the first one is selected (and becomes selectedArtist).

As an UX improvement, the “inferred” range from the label in the input is selected, so that the user can continue typing and thus select another artist if the first one was not what they meant. (See when I type “J” in the demo).

Design concepts

I’m not totally happy with the current state of the component because of the following:

1) The auto-complete component reaches inside the auto-complete-input one (set in its input property) to call setSelectionRange on it (see the last code snippet).

2) The same component retrieves the options from the list and gets its item to select it. Again, this is quite intrusive and will break if the internals of auto-complete-option change.

3) Still the auto-complete component yields an instance of itself as a block parameter. This enables “downstream consumers” to access any of its properties and methods, breaking its encapsulation.

In presenting about these concepts at the Global Ember Meetup and at Ember.js Belgium, I said that I like to think about components as the objects of the UI. Thinking about them as objects helps to deliver the point that some (most?) object oriented practices should be applied to components, too. If this assumption is correct, we can leverage OOP design concepts and guidelines that we’ve been developing for decades, giving us a headstart on how to design (and what to watch out for) complex component hierarchies.

For example, I consider the set of block parameters yielded by a component as its public API. This means that yielding this from a component’s template is considered bad practice as it breaks encapsulation. In some cases, it’s relatively easy to find a way around it, in others it’s much more difficult. We’ll see if I can pull it off in the above case.

As a closing thought, notice how 95% of the feature’s implementation relied on block parameters and closure actions. They are fantastic tools to work with and I don’t know how anything could be achieved without them before they existed.

Pointers

Incidentally, Miguel Camba seems to think about components lately, too. I already mentioned his fantastic talk at EmberCamp this year called “Composable components”, but above that he has released ember-power-select, which serves the same purpose as the auto-complete component in my blog post series.

However, it’s much more mature and flexible so if you need a select dropdown in your app, use ember-power-select, as my component is for learning and demonstration purposes only. That said, I published it on Github under balinterdi/ember-cli-autocomplete if you want to take a look or follow along the blog posts while looking at its source code. I put a tag called ccd-part-one on the repo for this blog post.

In the next episode…

… of the series, I’d like to address (some of) my concerns I mentioned above and see how to fix them. Stay tuned!