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!

The Path to Contextual Components: Contextual Components

Contextual components are introduced in Ember.js 2.3 to improve component composition and the usage of components for DSL.

Contextual components bring three new features to Ember:

  1. The hash helper: this simple helper just returns a hash with the attributes passed to it.
  2. Closure components: similar to closure actions, a closure component encapsulates a component with a set of attributes for later reuse.
  3. Dot syntax: any identifier with a dot (.) whose context is a closure component will be rendered as the component itself.

Using the hash helper

The hash helper, though simple, is a fundamental element of this new feature as we will see in the third part. Invoking the hash helper with a collection of key-value pairs returns a hash with the given collection as content.

{{! app/templates/components/my-hash.hbs }}
{{yield (hash text="my-text-component"
checkbox="my-checkbox-component"
textarea="my-textarea")}}

This component will yield a hash with the passed key-value pairs. The following template:

{{#my-hash as |f|}}
{{f.text}}
{{f.checkbox}}
{{f.textarea}}
{{/my-hash}}

would produce three lines with the names of the components: my-text-component, my-checkbox-component, and my-textarea.

Twiddle

Creating a closure component

The first part of using contextual component is the ability to create a closure around a component and a set of parameters and attributes. The {{component}} helper has been modified to be able to render these closures too.

Creating a new contextual component is very easy: you just need to use the component helper!

Let us create our first contextual component:

{{! app/templates/components/my-greeter.hbs }}

{{yield (component (if formal "formal-greeting" "informal-greeting") name=name)}}
{{! app/templates/components/formal-greeting.hbs }}

Good morning, Mr/Mrs {{name}}!
{{! app/templates/components/informal-greeting.hbs }}
Hey, {{name}}, how's it going?

Then, we can render our closure using the component helper as in the following snippet:

{{#my-greeter formal=true name="Sergio" as |greet|}}
{{component greet}}
{{component greet name="Matthew"}}
{{/my-greeter}}

In my-greeter, (component ...) creates a closure over the inline if and name="Sergio". This closure is passed as first argument to the block. Then, when rendered with {{component greet}} would be like invoking it as {{component "formal-greeting" name="Sergio"}}. In the second example, the passed name is Matthew, the most recent value to the name attribute.

As with the usual {{component}} helper, the rendered component can change just by changing the first parameter to the helper. You can play with this twiddle to see basic contextual components in action.

Rendering a closure component without the component helper

Remember when I said that the hash helper, though simple, is a fundamental element of contextual components? Well, this is the part everything comes together. The best part of contextual components is that, if Ember finds and invocation with a point in the name containing a closure component, it will render the component. As an example:

{{! app/templates/components/form-for.hbs }}

{{yield (hash text-field=(component "my-text-field" object=object)
text-area =(component "my-text-area" object=object)
checkbox =(component "checkbox" object=object))}}
import Ember from 'ember';

const FormForComponent = Ember.Component.extend({});

FormForComponent.reopenClass({
positionalParams: ['object']
});

export default FormForComponent;

The FormForComponent is quite simple. It has a positional parameter mapped to object (just not to call it model) and exposes a hash with three different components. Then, we can invoke those components by using the dot syntax for contextual components:

{{! app/templates/your-route.hbs }}

{{#form-for model as |f|}}

{{f.text-field "name"}}
{{f.text-area "lastName"}}
{{f.checkbox "lovesEmber"}}

{{/form-for}}

When Ember finds a dot in the first part of a moustache, it checks if the value is a contextual component. If it is, Ember is intelligent enough to unbox it and render it.

Full example

Previous example needs some more code to be fully functional. The rest of the code can be found in this twiddle. There is an interesting trick being done there: you can use the mut helper with a get helper inside to make a mutable binding to a variable property of an object.

You can see the code in this gist as well.

Gotchas

  1. While passing null or undefined as component name to the component helper will render nothing, passing it to a closure component will raise an error.
  2. Conflicting positional parameters and attributes usually raise an assertion. When working with contextual components, a collision between current parameters and attributes with those from the closure won’t raise an error.

The Path to Contextual Components: Understanding dynamic components

The component helper allows the user to render a component based on a given name.

Given these two components:

{{! app/templates/components/first-component }}

This is first-component

{{name}}
{{! app/templates/components/second-component }}

This is second-component

{{name}}

We can render dynamically any of them with the following invocation:

{{component componentToRender name='Sergio'}}

The first parameter contains the name of the component you want to render. In this case, either 'first-component' or 'second-component'. The {{component}} helper passes everything but the first parameter to the component. In the example above, the rendered component receives name='Sergio'.

This twiddle demonstrates how to change the rendered component by changing a variable.

Rendering inputs and textareas

As of 2.3, inputs and textareas components cannot be rendered using dynamic components. The reason is simple: neither {{input}} nor {{texturea}} are components but helpers. There is a simple workaround for that. input is a helper around two components: Ember.TextField and Ember.Checkbox. Thus, we can extend those components just by creating new ones with no template and the following content:

// app/components/my-text-field
import Ember from 'ember';

// If you want to re-export it
export default Ember.TextField;

// If you want to extend it
export Ember.TextField.extend();
// app/components/my-checkbox
import Ember from 'ember';

// If you want to re-export it
export default Ember.Checkbox;

// If you want to extend it
export Ember.Checkbox.extend();

For textarea the code is pretty similar but with Ember.TextArea as the underlying component.

This twiddle demonstrates how it is done. There is a difference between using this technique and using a bound attribute to type. The former rises an assertion when using checkbox and a value attribute, like demonstrated in this twiddle.

Gotchas

  1. If null or undefined are passed as a component name, nothing is rendered.
  2. If a invalid or nonexistent component name is passed, an error is raised.

Go to the last post: Contextual Components!

The Path to Contextual Components: Understanding positional parameters.

Ember.js 2.3 introduces a brand new feature: contextual components. This feature aims to better component composition and the usage of components for DSL.

Before diving into this new feature, we will go through some of the details of current components invocation. In this post, we will see how positional params are used.

Positional params Vs. Attributes

Positional params are the secret behind liquid-fire or the new link-to component. It allows the programmer to automatically assign to an attribute the value of a positional parameter.

Positional params are declared as class properties in the component. The value of positionalParams will determine how and to which attributes the parameters will be mapped to.

There are two types of positional parameters: named and rest positional parameters.

Named positional parameters map one value to one attribute. Let’s see and example:

// app/components/my-if.js
import Ember from 'ember';

const { computed } = Ember;

const MyIfComponent = Ember.Component.extend({});

MyIfComponent.reopenClass({
positionalParams: ['predicateValue']
});

export default MyIfComponent;
{{! app/templates/components/my-if.hbs }}

{{#if predicateValue}}
Yay!
{{yield}}
{{ else }}
????
{{yield to="inverse"}}
{{/if}}

We are setting positionalParams to an array containing only one value: the string 'predicateValue'. When the component is rendered, Ember.js extracts and process the positional params, setting the property in the component’s instance.

Calling the component using this syntax {{my-if booleanValue}} is virtually the same as calling it with an explicit attribute {{my-if predicateValue=booleanValue}}.

Then, we can use this new component like the following example:

{{! app/templates/application.hbs }}

{{#my-if currentBooleanValue}}
This is true
{{ else }}
This is false
{{/my-if}}

You can see a complete working example in this twiddle (files in this gist).

When you don’t know the number of parameters

Some components behave in different ways depending on the number of parameters. LinkToComponent treat a parameter in different ways depending on the number it received.

Rest positional parameters sets the property in positionalParams to an array containing all the properties.

Let’s take a look at the list-properties:

// app/components/list-properties.js
import Ember from 'ember';

const { computed } = Ember;

const ListPropertiesComponent = Ember.Component.extend({
object: computed.readOnly('params.firstObject'),
properties: computed(
'params.[]',
{
get() {
let [,...props] = this.get('params');
return props;
}
}
)
});

ListPropertiesComponent.reopenClass({
positionalParams: 'params'
});

export default ListPropertiesComponent;
{{! app/templates/components/list-properties.hbs }}

{{#each properties as |prop|}}
{{yield prop (get object prop)}}
{{/each}}

And then use it in a template in the following way:

{{#list-properties obj 'a' 'c' 'b'
tagName='dl' as |key value|}}

{{key}}{{value}}

{{/list-properties}}

In this example, positionalParams property is just a string. Ember.js will take the array of all positional parameters ([obj, 'a', 'b', 'c']) and set it in the params property in our component’s instance. Then we create two computed properties, object and properties, based on the head and tail of said property.

Gotchas

  1. If a component invocation finds both a positional parameter and an attribute for the same property, then an assertion is raised.

Follow up in Understanding dynamic components and Contextual Components!