UPDATE: I recently published a WintellectNow course (“Getting Started with Ember.js”). Use promo code NSTIEGLITZ-13 for a free two week trial.
—
Ember Components are “custom HTML elements that you can use to clean up repetitive templates or create reusable controls.” They simplify your templates by encapsulating functionality and introduce reusability that would otherwise not be possible.
If you’ve been reading about HTML5 Custom Elements, this concept should not be totally unfamiliar. It should however, sound awesome. Ember.js components are future-proof future-resistant in that the Ember.js team is working with the W3C standards body to ensure an easy migration path to the HTML5 custom element.
If you’re brand new to Ember.js, check out Getting Started with Ember.js on WintellectNow (shameless plug – use promo code NSTIEGLITZ-13 for a free trial with Credit Card), and read the ever improving guides on Emberjs.com. Otherwise, read on to learn how to build an Ember Component.
In this blog, I’ll walk through building a star rating component and show you how to consume the component in a simple demo app. The demo app (source code linked below) allows users to rate movies, but the star rating component is generic enough that it can be used to rate anything (products, beer, etc). Below is a screenshot of the demo app. The gold star below each movie title is an instance of the star rating component.
Defining the Star Rating Component
There are two parts to a component: the handlebars template and the JavaScript object which extends Ember.Component.
The Handlebars Template
First, let’s build the template. By convention, component templates are named with the prefix component/. If you’ve been working with Ember for more than a few minutes, you understand the importance of naming. Our star rating component is named component/star-rating.
<script type="text/x-handlebars" id="components/star-rating"> <ul style="padding: 0"> {{#each star in stars}} <li style="color: #FFD700;" {{bind-attr class=":glyphicon star.empty:glyphicon-star-empty:glyphicon-star"}} {{action click star}}></li> {{/each}} </ul> </script>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/white-space: pre;/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/white-space: pre;/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
The template binds to the stars property on our Ember.Component (which we will define next). If the star.empty property is true, the <li> content will be an empty star icon, otherwise, it will output a filled star. We will handle the click action in our Ember.Component, passing in the current star as an argument.
Extending Ember.Component
Next we need to define the component. This component is responsible for providing properties to the template for binding and to translate DOM events into semantic events. In other words, the component can translate something like a click event, and give it meaning in the context of your application. In the code below, our component will translate a DOM click event into a semantic “Update the star rating” event.
App.StarRatingComponent = Ember.Component.extend({ maxStars: 0, starRating: 0, stars: [], actions: { click: function(star){ this.set('starRating', star.index); this.sendAction('action', star.index); } }, setRating: function() { var stars = [], i = 0; var starRating = this.get('starRating'); for(i = 0; i < this.get('maxStars'); i++){ stars.pushObject(Em.Object.create({empty:i >= starRating, index:i+1})); } this.set('stars', stars); }.observes('starRating').on('didInsertElement') });
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/white-space: pre;/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Like all Ember Components, we start by extending the Ember.Component. Again, note the naming. Because we are using Ember idiomatic naming convention, Ember will know the components/star-ratingshould bind to the App.StarRatingComponent.
- click action – Translates a DOM click event to a semantic “Update the star rating” event”. We use the index of the star to update the starRating and call sendAction so the application can respond accordingly to a star rating change.
- setRating – Observes the starRating property and updates the stars property when it changes. We also fire this event on didInsertElement, which fires after the template has been inserted into the DOM.
Sweet. Now we have a component. Let’s wire it up…
Using the Star Rating Component
You use a component the same way you would use a handlebars helpers. Here is a snippet of template that uses the {{star-rating}} component.
<script type="text/x-handlebars" data-template-name="movie">
class=“movie-item”>
{{movie.title}}
{{star-rating starRating=movie.starRating maxStars=movie.maxStarRating action=“rateMovie”}} …the rest of the code omitted…
</script>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/white-space: pre;/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/white-space: pre;/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/white-space: pre;/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
In this code we are binding the component’s starRating and maxStarRating properties to the respective model movie.starRating and movie.maxStarRating. More interestingly, we are binding the component’s action to a rateMovie function defined on the controller. You can also pass parameters into the action, but we don’t need any in this case. The rateMovie function can respond (update the model and persist to the server, etc.) whenever our component changes.
Wrapping Up
Are you as impressed as I am with how little code that took? We now have a decent rating component which can be reused across a number of Ember applications. Even more impressive, is that this component could easily be refacted to the HTML5 equivalent when the standards are implemented.
Be sure to read the Ember.js guide to Components. The guide dives a lot deeper than I do in this brief blog.
The star rating component and movie rating demo app code is available on github. Enjoy!