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:
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:
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:
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.
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.
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.
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.
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.
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.
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:
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:
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):
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:
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:
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:
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.
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:
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:
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.
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:
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:
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:
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:
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.
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:
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.
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:
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):
123456789101112131415161718192021
// addon/components/auto-complete.jsexportdefaultEmber.Component.extend({(...)keydownMap:{8:'startBackspacing',// backspace13:'selectOption',// return27:'closeDropdown',// escape38:'focusPrevious',// up key40:'focusNext',// down key},handleKeydown:Ember.on('keyDown',function(event){constmap=this.get('keydownMap');constcode=event.keyCode;constmethod=map[code];if(method){returnthis[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:
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:
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.
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:
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:
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:
(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!
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:
(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:
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:
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:
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:
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:
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:
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:
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:
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:
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.
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:
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!