A simple start

I was recently working on a UI element that worked a lot like an accordion. What, you may ask, is an accordion in my eyes?

  • The user sees a list of items. Items can be expanded to show details or collapsed to show less information.
  • Only one item can be expanded at any given time. Expanding one item will collapse another.

It’s probably reasonable to have a component that represents the whole accordion. As long as there aren’t
a huge number of items to show it’ll be worthwhile to represent each item in the accordion with its own component as well.
For context, here’s the application template:

For the purposes of this example, an item is just a POJO with a title and details, as shown here in the application controller:

Given I’m creating a component for the list and a component to render each item, the real question in my mind is how to keep just one
item open at a time. I did this problem as a kata at Ember Columbus Meetup and we all
came up with similar solutions (which isn’t to say there aren’t more possibilites): have the list component keep track of which item is expanded,
and then pass that information into each accordion item component.

Imagining that each item in the accordion gets its own component, a basic template for the item might look like this:

Here’s the component file.

In order to expand just one item exclusively, each component checks if its item is the active one.
The accordion component manages that piece of state and passes it into the child. The child sends and action back up to the parent
in order for the parent to change its state.

Here’s an ascii diagram of the data down and the actions up.

Here’s an Ember Twiddle of my initial implementation.

The accordion as a pattern

For me the accordion turned out to be an expression of a pattern I see pretty frequently when I work with lists.

  • Both the list itself and each position in the list are represented by components.
  • Each child component in the list receives both the item it should display as well as the whole list.
  • The child component decides what to do with the information, rather than having the parent do those computations.
  • Children send an action to the parent to change any shared state.

By generalizing a little, component hierarchies that look quite different in the UI might end up behaving in very similar fashion.
The unfamiliar becomes familiar! Now I can look at translating the pattern into a more reusable component.

Separating behavior and rendering with {{yield}}

I started out with a generic idea but quickly got specific in the implementation, and it shows. My accordion only renders one kind of item, and other than passing it a
different list of items in there’s no real way for it to be used in other contexts. That’s an okay start, though! On the plus side,
the accordion encapsulates everything away from its caller. It only takes one parameter so it’s easy to invoke, and it’ll serve as a good boundary for testing.
I feel like creating a new component is always a tradeoff between encapuslation and indirection.
Right now both the rendering logic and the mechanics of the accordion itself are hidden away. It’s nice as a caller because the
accordion-list is only exposing one parameter to the outside world, but as a reader it’s frustrating to have to navigate a whole
hierarchy of black boxes to understand what’s going on behind the scenes.

What I really want is something to take care of the accordiony behavior of the UI without enforcing how I render the parts.
Ideally the caller could render the list and specify how the items look the items in one fell swoop.
In Ember, when a component needs to let its caller provide it with content, the {{yield}} helper is the thing to use.
The guides have some good examples, and I’ll
be going over the useage here too.

From this point on I’ll make a few versions of the accordion-list component. I’ll call the first one block-accordion.
Here’s a first shot at what I’d want the API to look like:

I’ve pretty much just inlined the old accordion-item template into the calling context now. I worked backwards from the template
to decide that block-accordion will have to yield the item and isExpanded into the block.

Here’s the accordion rendering accordion-item behind the scenes:

Maybe I can get rid of the accordion-item component
entirely? accordion-item contained the logic for isExpanded, and if I decided to nix it I’ve got a few choices.

  • I could use a handlebars helper to check if the activeItem is the same as item ala
    ember-truth-helpers and put the logic into the template. Since
    it’s just an equality check isExpanded=(eq item activeItem) would be easy to reason about.
  • I could calculate isExpanded for each item inside the accordion component, and then somehow yield it.
  • I could keep accordion-item and yield isExpanded from there.

For now I’m going to keep the accordion-item around. It’s serving as a glorified helper for the moment, but it’s a good example of yielding
content to a nested component, and it’ll be easy to remove at the end if it still feels redundant.

Now the accordion itself doesn’t know anything about how the actual items are rendered, and leaves it up to the caller to specify.
That includes implementing the action to toggle the active item, and when to hide or show the details. As a reader, it’s hard to tell what
portions of the template are devoted to the accordion itself and what is for specifics of the header and the item. It’d be nice if the accordion
could also provide those behaviors to the caller and still allow for flexibility in rendering the items. As of Ember 2.3 there are some declarative
ways to do exactly that!

Encapsulating behavior with contextual components

As of Ember 2.3, a component can yield other components in its block params.
The guides
provide a few examples, but I’ll be going into it in a litle more detail here. Coming from a rails background,
I was used to building markup using form helpers that yield a form builder to a block. Something
like this:

Inside the block, person_form.text_input is a function that’s going to render some of its own markup. The form builder essentially exposes
its own DSL for building forms.
This is totally possible in contemporary Ember! I’ll go over the two building blocks: the hash helper, and contextual components.

The hash helper

The hash helper allows you to create hash inside a template.
Rather than having to yield a long list of parameters you can simply yield a hash and access the params by name.

Contextual components

A contextual component
is a component that was created with the component helper and yielded as
a parameter. When a caller invokes the yielded component they can specify extra options, just like with a normal component. Here’s a simple version of the
rails form builder implemented in handlebars as an example.

Not that for the text-input component, the model is passed in by the form-for component, but the field is passed in later. Also note that the
internal details of how the submit-form component’s action is handled are completely hidden from the caller. I could make calling the form
builder even nicer by using positional params,
but this example shows how much you can accomplish only using templates.

If I apply the same methodology to the accordion, I can provide some declarative, semantic components that will make it harder
to use the accordion incorrectly. I’d like to have the accordion take care of expanding an item when a user clicks. I’d also like
to encapsulate the logic for hiding and showing the details based on the isExpanded value. I think I can use two new contextual
components to do that!

I’ve changed my items to be a list of blog posts:

Here’s the template for rendering the new accordion:

From the caller’s point of view, I think this is much improved. Everything the caller needs to maintain the accordion itself has
been yielded as a component. The header component takes care of toggling the active item, and the details component
hides or shows the provided content. The two contextual components also provide clear buckets for me to enter the custom
markup for my blog posts. I was able to render a totally different kind of item without having to change the accordion at all,
and I don’t get lost reading through it either. How is this working behind the scenes?

Admittedly this is harder to read through than the first two versions of the accordion. contextual-accordion yields two params: the item to render, and then a
hash with three members. isExpanded is the yielded parameter from accordion-item, and then there are two contextual components. As components go, there’s nothing
special about them. accordion-header gets its click action set when it’s invoked from accordion-item.

click is one of the many events that
Ember.Component inherits from Ember.View that you can register actions on.

As for the header template:

The template itself renders another div with a class used for styling, and that’s it!

The details template has only a little more content:

accordion-details holds an if based on isExpanded, which got passed in by the accordion.

In my case the components themselves are very simple, but by wiring them together inside the accordion component they can expose my intent to the
caller without also exposing their implementation details. Here’s a link to the updated twiddle

Recap: From single-use to generic

I started with two components, accordion-list and accordion-item. If someone wanted to change how the list was rendered, they would’ve had to make completely new components.
By changing the accordion components to yield values, I made something that was much more flexible, but the component didn’t provide much guidance to other developers. The contextual
version of the accordion is still flexible, but it also provides a toolbox that represents its domain. On the negative side, the internals of the component itself are less straightforward than
the other two implementations, and the number of files has gone up. There’s some extra indirection but I feel like the extra power and guidance that the contextual components provide are more than
worth it.

Free Software planner

Gain access to the same planning process we use to prepare our clients.