Before Splats. Spoiler: it is all about tagless components

There is a recurring question about components that appear in Slack: how can I pass different attributes to a component depending on a given object?

As an example, people want to be able to do something like the object spread operator in ECMAScript.

Object Spread Properties

The TC39 proposal Object Rest/Spread Properties for ECMAScript gives us programmers the ability to use the rest and spread operators with objects.

let defaults = { name: "No one", location: "Braavos" };
let arya = { ...defaults, name: "A girl" };
console.log(arya.name); // A girl
console.log(arya.location); // Braavos

let jaqen = { name: "A girl", ...defaults };
console.log(jaqen.name); // No one
console.log(jaqen.location); // Braavos

In these two cases, the spread operator works pretty much like Object.assign:

let arya = Object.assign({}, defaults, { name: "A girl" });
let jaqen = Object.assign({ name: "A girl” }, defaults);

Wouldn’t it be great if we could just {{component varComponentName …someObject}}?

Spread in Handlebars

There is a proposal for adding the splat operator to Handlebars. You can follow the discussion on the original issue opened by mixonic or the PoC implementation by machty. The last I heard is that this feature is on hold because of performance concerns.

But I need a solution!

Let’s try to fix this problem. For that, we will create an interface for a mili-blogging (more than microblogging but not as huge as blogging) network with several (read as “at least three”) post types. Let’s call this network stumblr.

You can blog text, images and quotes in stumblr. These three types are defined as follow in the JSON payload (the Ember Data models can be found in the twiddle linked later):

// Text
{
  id: "some-text",
  type: "text",
  attributes: {
    text: "Verde que te quiero verde. Verde viento. Verdes ramas."
  }
}

// Quote type
{
  id: "some-quote",
  type: "quote",
  attributes: {
    quote: "There's one thing I always wanted to ask Jack. Back in the old days. I wanted to know about that Doctor of his. The man who appears out of nowhere and saves the world; except sometimes he doesn't. All those times in history where there was no sign of him … I wanted to know why not. But I don't need to ask anymore. I know the answer now: Sometimes the Doctor must look at this planet and turn away in shame. I'm recording this in case anyone ever finds it, so you can see. You can see how the world ended.",
    author: "Gwen Cooper"
  }
}

// Image Type
{
  id: "some-image",
  type: "image",
  attributes: {
    src: "http://my-mili-cdn.org/my-image.png",
    caption: "Some caption"
  }
}

(I know this is not valid JSON but each part is a valid JSO, close enough for me.)

We define three components, one for each type:

// Assuming the template is {{text}}
import Ember from 'ember';
export default Ember.Component.extend({
  tagName: 'article',
  classNames: ['stumblr-text']
});
// Assuming the template is
// <p class="quote-body”>{{quote}}</p>
// <p class="quote-author”>{{author}}</p>
import Ember from 'ember';
export default Ember.Component.extend({
  tagName: 'article',
  classNames: ['stumblr-quote']
});
// Assuming the template is
// <figure> 
//   <img src={{src}} />
//    {{#if caption}}<figcaption>{{caption}}</figcaption>{{/if}}
// </figure>
import Ember from 'ember';
export default Ember.Component.extend({
  tagName: 'article',
  classNames: ['stumblr-image']
});

We now have one component for each post type and we can render them statically just by invoking them, as usual. You can play with the result in this twiddle.

In this example we are very restrictive: we are assuming we will always receive an image, a quote and a text. What if our user used stumblr strictly as a photoblog?

Since we will need to choose the component based on a model we can used a property to choose between several options. As an example, we add the property componentName to the Image model:

export default DS.Model.extend({
  componentName: 'dynamic-stumblr-image'
});

We can now render the right component just using the {{component}} helper:

{{component postModel.componentName post=postModel}}

If you want to know more about the {{component}} helper, read my post about it: The Path to Contextual Components: Understanding dynamic components. Notice that we are not restricted to a property in the object to return the name for the component: we can use a helper too.

We will use dynamic-stumblr-image to set the right attributes in stumblr-image.

{{! template for dynamic-stumblr-image }}
{{stumblr-image
    src=post.src
    caption=post.caption}}

Repeat this for the rest of components and change the previous template to:

{{#each model as |item|}}
  {{component item.componentName post=item}}
{{/each}}

and you will get a pretty similar result. Examine this twiddle to see the rest of the changes (new dynamic-stumblr-foo components and new model hook in the route).

Still not there

Though it seems so, we are not producing the exact same output: our dynamic-stumblr-foo components produce an extra tag around the stumblr-foo version. Let’s get rid of that.

In a component, the property tagName let us select which tag Ember will use to surround our template. If we set tagName to ul we will make our component a list, article would make it an article, null will make Ember use div. But if we want Ember not to use any tag at all, what should we do then?

River Song always have a way out

Set tagName to ’’ (empty string). This is our way of asking Ember to get a tagless component. This has some limitations: an id is not guaranteed to get generated and you cannot expect it to call event methods like click, focus and such.

You can see the final result in this twiddle.

Final notes

There are more options here too. Creating a blueprint for component wrappers should not be too difficult. Or maybe your application does not need the duplicated components at all!

Hope you enjoyed it!

Author: Serabe

Mathematician, and Ruby and JavaScript programmer. Sometimes I speak at conferences and local meetups.

2 thoughts on “Before Splats. Spoiler: it is all about tagless components”

Leave a Reply

Your email address will not be published. Required fields are marked *