Let's see how templates fit in to our Ember.js application. When you create a new application using Ember CLI, you will find the app/templates
folder inside your application. This is where the Ember.js resolver looks for template files for your project.
Let's look at the default application.hbs
template present at app/templates/
:
<h2 id="title">Welcome to Ember.js</h2> {{outlet}}
The default application template is present at chapter-3/example1/app/templates/application.hbs
The application template is the main template that is rendered when the application starts. The most common use case for application templates is to put in the header and footer of the application here. This is the entry point for the application and it makes sense to put in sections of the application that will remain visible throughout the application here. All the routes that are rendered will be nested under the application route. We will talk more about the routes and naming conventions used to associate templates with model, views, and controllers in Chapter 4, Managing Application State Using Ember.js Routes.
The application template should also contain at least one {{outlet}}
helper. The outlet helper tells the router to render the next appropriate template in place of the {{outlet}}
helper. The next template is usually resolved from the routes and URL convention. We will be discussing more about the convention router in Chapter 4, Managing Application State Using Ember.js Routes.
Let's change the application template to include our headers and footer notes. Let's also render the following templates between header and footer by placing the {{outlet}}
helper in between the header and footer tags:
<header> <h2>Welcome to Ember.js</h2> </header> <div> {{outlet}} </div> <footer> ©2014 Ember.js Essentials </footer>
The modified application.hbs that now includes header and footer information is present at chapter-3/example1/app/templates/application.hbs
As you can see, here we have moved the outlet tag between the header and footer. Let's create a new index template that now renders between the header and footer when we access the http://localhost:4200/
URL. We will be discussing more about how the routes match and resolve the template names in Chapter 4, Managing Application State Using Ember.js Routes. Till then, it is safe to assume that when you open the application index URL in the browser, the application template is rendered, which, in turn, renders the index template in place of the
{{outlet}}
helper.
To create the index template, let's create a new file index.hbs
inside the app/templates/
directory with the following content:
<ul> {{#each item in model}} <li>{{item}}</li> {{/each}} </ul>
The contents of index template are present in chapter-3/example1/app/templates/index.hbs
Now, if you run chapter-3/example1
by running the ember serve
command in the terminal and open the http://localhost:4200/
URL in your browser, you will see the following output:
Here, we have used a simple header and footer, but you could potentially use a more complex and better UI for your application.
Your template can also have more than one outlet. In such cases, outlets are usually named so that you can tell the router which outlet to render your template, as follows:
{{ outlet "sidebar"}}
We will be discussing more about named outlets when we discuss routes in Chapter 4, Managing Application State Using Ember.js Routes.
By this time, you would have figured out that Handlebars.js expressions are wrapped in {{}}
. The Ember.js framework provides us with two initial routes: the application route and index route. The application route renders the application template present at app/templates/application.hbs
when the application initializes. The index route is activated when the users visits the /
or the index of the application. It renders the index template from app/templates/index.hbs
. If you need any custom behavior other than what's already there, you will have to provide your implementation of the routes. Template will look for data from its associated controller. "Convention Over Configuration" governs the association between a controller and template. You don't have to manually wire up things here.
In our case, the index
template will look for data from the index controller present and exported from app/controllers/index.js
. But we have not defined the index controller anywhere. Whenever the Ember.js framework cannot find the required component, it will try and generate one for you. In our case, even though we have not defined the index controller, the framework will not complain and generate one for you. This generated component will have the default behavior that basically does nothing. This makes our code clean and saves us from writing components that do nothing.
Let's look at the index
template present at chapter-3/example1/app/templates/index.hbs
:
<ul> {{#each item in model}} <li>{{item}}</li> {{/each}} </ul>
You can see here that we are iterating items present in model array. Now, as we discussed above, the template will look for controller to provide the data. Now, since the "model" attribute is not present in the default index controller implementation, it should be set externally from somewhere else.
Let's look at the index route present at chapter-3/example1/app/routes/index.js
:
import Ember from 'ember'; export default Ember.Route.extend({ model: function() { return ['red', 'yellow', 'blue']; } });
You can see here that we are returning an array containing three colors, ['red', 'yellow', 'blue']
, from the model
method. Doing this, the framework will automatically set the model
property on the corresponding index controller to ['red', 'yellow', 'blue']
.
That's why we do the following:
{{#each item in model}} <li>{{item}}</li> {{/each}}
In the index
template, the model
property is fetched from the index
controller that is set from the index route by the framework.
Normally, a model
method would return the data that was fetched from the server and the properties that don't have to be persisted to the server and are part of the controller definition.
Here, if we wanted to access some properties that are part of the controller, we will be accessing them directly by their name in the template. Let's see this in an example.
Now, since we want to access custom properties from the controller, we will have to define them first, as shown in the following:
import Ember from 'ember'; export default Ember.Controller.extend({ name: "Suchit Puri" });
The index controller present at chapter-3/example1/app/controller/index.js
Here, we have defined a name
property in the index
controller of our application; let's change the template to use this property, as shown in the following:
Hi, this is {{name}}. I like the following colors. <ul> {{#each item in model}} <li>{{item}}</li> {{/each}} </ul> </script>
The index template of our application using properties from the controller, the index template can be found at chapter-3/example1/app/templates/index.hbs
Here, you can see that when we use {{name}}
in the template, it will look for that property in the index controller, which would return the correct name. Now, the name
property present in the index
template is bound to the template, which means that if you change the name
property of index
controller, the change will automatically be reflected in the template.
Now, since the basics for Handlebars.js expressions are clear, let's jump into the detailed syntax of the Handlebars.js template and see how easy it is to create custom templates for your application.
Handlebars.js does not promote the use of complex business logic inside your templates. It makes it difficult to mix complex business logic in the templates by providing a very limited set of helper and scope methods. This makes your templates very clean and decoupled from the business logic.
Using complex business logic in your templates makes them very difficult to understand and debug. In any case, writing business logic in templates is a bad design choice and violates the separation of concerns (SoC) design principle, which states: "In computer science, separation of concerns (SoC) is a design principle for separating a computer program into distinct sections, such that each section addresses a separate concern."
Let's look at the if
, else
, and unless
conditionals in Handlebars.js.
Frequently you will run into situations where in you would want to show or hide part of the template based on some condition that returns a boolean result. Handlebars.js conditionals are made exactly for the same purpose. The if
conditional is a Handlebars.js helper method, which will execute the code block enclosing it when the condition is true, else
will render the contents of the else
block. Let's see this by looking at the following example:
{{#if edit}} <ul> {{#each item in model}} <li>{{item}}</li> {{/each}} </ul> {{/if}} <button {{action 'changeEdit'}}>Toggle</button>
The index template is present at chapter-3/example2/app/templates/index.hbs
As you can see in the preceding code, we have moved the earlier content of the index template of example1
inside the if
condition. We have also added a toggle button that will toggle the property of the edit
flag when the button is clicked. We are using the {{action}}
helper method to trigger an event on the click of the button. We will be talking more about the {{action}}
helper later in this chapter, but till then it's safe to assume that on clicking the button will trigger an action changeEdit
on the index
controller.
Let's look at index
controller next; as we discussed in the previous sections, a controller backs the respective template to supply the bound properties. So, in order to support the preceding index
template, we will have to define the actions and properties inside the index controller that can then be used inside the template, as shown in the following:
import Ember from 'ember'; export default Ember.ObjectController.extend({ edit: true, actions:{ changeEdit: function(){ this.toggleProperty('edit'), } } });
The index controller is present at chapter-2/example2/app/controller/index.js
As you can see, the index controller has an edit
property set to true
. It also has an actions
object that contains the implementation of our custom actions. This implementation is called whenever the user uses the {{action}}
helper in the template.
So, whenever the user clicks on the Toggle
button, it triggers the changeEdit
action event, and this event triggers the changeEdit
method in the actions
object that toggles the edit
property of the controller. Now, since the template is bound to the edit
property of the controller, it re-evaluates itself and show/hides the HTML present inside the {{#if}} {{/if}}
block.
An if
block can conditionally have an {{else}}
block, which will execute the block when the if
block evaluates to false
, like any other if-else block:
{{#if edit}} <!—do something here --> {{else}} <!—do something else here --> {{/if}}
The Ember.js framework also gives you an easy syntax to check for a negative of a boolean value; so, for example, if you wanted to check if some boolean value is not true, then only execute a block, which you could do by using the {{#unless}} {{/unless}}
block. The unless
helper behaves exactly similar to the {{#if}} {{/if}}
helper methods, the only difference being that it checks for the negation of the boolean value instead of the boolean value.
One interesting thing to note here is that {{#if}}
and {{#unless}}
are examples of block expressions, which means that these helper methods allow you to execute your helper method on a portion of template. Such helper methods in the Ember.js template system begin with #
and require a closing expression to signify an end.
One of the common use cases in today's modern web applications is showing a list of data in a tabular form. Handlebars.js allows you to easily do that via the {{#each}}
helper.
By now, you might have observed that index
template in chapter-3/example1/app/templates/index.hbs
is a classic example of iterating a list of elements. Let's see this again in the following:
<ul> {{#each item in model}} <li>{{item}}</li> {{/each}} </ul>
In the preceding example you can see that we are iterating over an array called as model
. This model is set in index route, as shown in the following:
import Ember from 'ember'; export default Ember.Route.extend({ model: function() { return ['red', 'yellow', 'blue']; } });
When we say {{#each item in model}}
, we are looping over all the elements present in the array model
, and, in each iteration, we assign the current object to item
. In our case, the model
array contains just string names, but in real scenarios, the model can be much more complex and can contain complex JavaScript objects. Let's see this by an example: first, we need to change the index route to return a more complex object. So, instead of color names, let's return an array containing company information objects, as shown in the following:
import Ember from 'ember'; export default Ember.Route.extend({ model: function() { var companies = [{ "name" : "Google", "headquarters": "Mountain View, California, United States of America", "revenue":"59825000000" },{ "name" : "Facebook", "headquarters":"Menlo Park, California, United States of America", "revenue":"7870000000" },{ "name" : "twitter", "revenue": "664000000", "headquarters":"San Francisco, California, United States of America" }]; return companies; } });
The index route is present at chapter-3/example3/app/routes/index.js
Here we return an array of companies from our model function in index route, every company object is of the form:
{"name" : <<company name>>,"headquarters":<<headquarters>>, "revenue":<<revenue>> }
Now let's create the index
template to display the list of companies in a tabular form, as shown in the following:
<table id="t01"> <tr> <th>Company Name</th> <th>Headquarters</th> <th>revenue</th> </tr> {{#each item in model}} <tr> <td>{{item.name}}</td> <td>{{item.headquarters}}</td> <td>{{item.revenue}}</td> </tr> {{/each}} </table>
The index template is present at chapter-3/example3/app/templates/index.hbs
Here, we define a table in plain HTML markup language with our headings as Company Name
, Headquarters
, and revenue
. Since each row in our table should display the respective company information, we will have to iterate our model array and for each record, we will have to create a new row in our table with three columns having respective data items.
Doing this will produce a table similar to that shown in the following image:
As you saw in the above example, it is easy to display a list of objects in a tabular form. You can see that the display logic is completely unaware of how to fetch the data. Like in our case, we have hardcoded the list of company information, but in a real scenario, you would be fetching the company list from a backend server. Doing this would only affect the implementation of index route's model
method and everything else remains exactly the same.
The other benefit you get out of this approach is that the template is bound to the items present in the model array, which means that when you add or remove a company item from the model array, the view will render the company list accordingly.
In the last section, we saw how to bind values from the model object within an HTML tag. But sometimes, you may want to bind the attributes, instead of value, of an HTML tag. For example, you may want to bind the class
attribute of a <div>
tag because you want to style elements differently based on some logic that is accessible by the controller.
Let's see that by an example, example4
. We will use the previous example chapter-3/example3
as our base.
Assuming that we have a requirement to show the headquarters
column text color based on some property in the controller. Handlebars.js allows you to do use the {{bind-attr}}
helper method to solve such situation. The {{bind-attr}}
method will bind the attribute name given next to a property, accessible by the controller or view:
<table id="t01">
<tr>
<th>Company Name</th>
<th>Headquarters</th>
<th>revenue</th>
</tr>
{{#each item in model}}
<tr>
<td>{{item.name}}</td>
<td {{bind-attr class="className"}}>{{item.headquarters}}</td>
<td>{{item.revenue}}</td>
</tr>
{{/each}}
</table>
<button {{action "toggleColor"}}> Change color </button>
The index template is present at chapter-3/example4/app/templates/index.hbs
As you can see in the preceding code, the only change from the previous example is that we have used the {{bind-attr class="className"}}
helper to bind the class of headquarters
column. The {{bind-attr class="className"}}
helper, will look for className
property in the controller to resolve the class name.
We have also added a new button at the bottom to trigger an action called toggleColor
in the controller, whose responsibility will be to change the className
property of the controller, based on some logic.
Till now, we have been using the default index controller, which is generated by the Ember.js framework in our examples. But now, as we need custom properties and action in our controller, we will have to explicitly define it:
import Ember from 'ember'; export default Ember.ObjectController.extend({ className:"red", actions:{ toggleColor: function(){ if(this.get("className") == "red"){ this.set("className","blue"); }else{ this.set("className","red"); } } } });
The index controller is present at chapter-3/example4/controllers/index.hbs
Here, you can see that we have defined a property called className
, which will be bound to the class attribute of the headquarters
column. We have also defined a new action, called toggleColor
, which changes the className
property from red
to blue
and vice versa.
The only thing left in our example is to define the two CSS classes, red
and blue
, which will set the color attribute to their respective colors. The CSS classes should be added to chapter-3/example4/app/styles/app.css
file, as follows:
.red { color: red; } .blue { color: blue }
Now, when you run the above example, you will see that the headquarters
column now is rendered in red
color. If you press the change color button, you will notice that the headquarters
color changes to blue. On subsequent presses, you will notice that the color toggles between red and blue.
The above example works because the {{bind-attr class="className"}}
helper binds the controller property className
with the class attribute of the <td>
tag. So, whenever the className
property of the controller changes, that change is automatically propagated to the class attribute of the <td>
tag.
You might be thinking, why can't you just use <td class={{controller.className}}>
or something similar, instead of using the {{bind-attr}}
Handlebars.js helper method? The answer to the above question lies in the understanding how Ember.js tracks which section of the HTML page is to be updated when the corresponding bound property changes.
Like in our case, the Ember.js framework will have to keep track of which section of my HTML page needs to be updated when someone changes the companies
array returned from model
method present inside the index controller.
If you see the generated HTML of the company's table, you will find that apart from the regular <table><tr><td>
tags, there are some additional attributes present in the <td>
tags, something like the following:
<td class="red" data-bindattr-258="258">Mountain View, California, United States of America</td>
These data bind attributes help the Ember.js framework to track which attributes to update when the corresponding bound properties in the controller changes.
Apart from data-bindattr
, the Ember.js framework also inserts special <script>
tags, like the following:
<script id="metamorph-0-start" type="text/x-placeholder"></script> <script id="metamorph-0-end" type="text/x-placeholder"></script>. To track the sections of HTML page it should update when the corresponding properties changes. So as a result when you do <td class={{item.name}}>
It leads to something like the following:
<td class="<script id="metamorph-0-start" type="text/x-placeholder"></script>Google <script id="metamorph-0-end" type="text/x-placeholder"></script>"
This leads to HTML that is invalid and leads to an error in our code as the value of the class attribute is invalid. This is the reason we need to use special Handlebars.js helpers instead of just using <td class={{item.name}} >.
Ember.js team has also extracted the preceding functionality into a separate library that is called as Metamorph.js. It can be found on GitHub at https://github.com/tomhuda/metamorph.js/. This is particularly helpful for people who are writing their own frameworks and want to use Metamorph.js to know which sections of the page to change when their corresponding JavaScript properties changes.
Ember.js provides another workaround the above problem by using the {{unbound}}
helper. We saw that the problem arises when the framework needs to bind the UI to JavaScript properties so that if the properties change the UI is updated accordingly. What the unbound helper does not bind the JavaScript properties to the UI and hence works fine even with the attributes; the only caveat there is that the properties are not bound to the UI and hence the UI is not updated even if the JavaScript property changes.
Handling user interaction is the core and the most important part of any modern web application. Showing, hiding, and deleting information, based on the user's interaction, is common to most web applications today.
We did the same thing in the Company brochure
example, where we changed the color of the headquarters
text, based on the click of a button. Though we have just changed the color of the text, but the possibilities are endless. You can show or hide elements at the click of the button, or change the full theme of the application by using the actions helper.
You may have noticed that the index
template where we created a button Change Color
triggers the toggleColor
action on the controller:
<button {{action "toggleColor"}}> Change color </button>
You can see here that we use the {{action}}
Handlebars.js helper method to trigger the action on the click of the button. Action helper can be used to make the HTML tags clickable.
When the user clicks on the element where the {{action}}
helper has been used, the helper triggers an event to the controller, like in case of chapter-3/example4
, the action triggers the toggleColor
event to the index controller. The controller needs to define a method with the same name as the event in its action object:
import Ember from 'ember'; export default Ember.ObjectController.extend({ className:"red", actions:{ toggleColor: function(){ if(this.get("className") == "red"){ this.set("className","blue"); }else{ this.set("className","red"); } } } });
The index controller is present at chapter-3/example4/controllers/index.hbs
Here you can see that we have defined an actions
object inside the index controller. The toggle method resides inside this actions
object.
Once the event is triggered from the DOM element, by default the event will go to the controller that is backing the template. If the controller does not implement the event handler method in its action object, the event will go to the route associated with the controller. If the route also does not implement the event handler method in its action object, it will go to the parent route and so on, till it reaches the application route.
This behavior gives us the ability to handle events at different levels in our application. For example, if there are some application-wide alerts or an error message box that needs to be shown, placing the error
event handler in the application route's action object will be enough to show the error message application-wide from one place.
Let us build an application-wide event handler that will show alert notifications throughout the application.
We will start with the templates first in our chapter-3/example5
, starting with the application template. The example5
is an extension from example4
:
<div {{bind-attr class="className"}}>{{message}}</div> <h2 {{action "alert" "something went wrong form the Application Template"}}>Company Information Brochure</h2> {{outlet}}
The application template is present at chapter-3/example5/app/templates/application.hbs
In the following, we have added a new <div>
element to house the alert:
<div {{bind-attr class="className"}}>{{message}}</div>
Here, we are using {{bind-attr}}
helper to bind the class name of the div
tag. The contents of the div
tag will be provided by the {{message}}
property from the application controller. By default, the div will be hidden, and when there is some action triggered, we will make this div
visible.
Take the following line:
<h2 {{action "alert" "something went wrong form the Application Template"}}>Company Information Brochure</h2>
We are using the action helper to trigger an action alert
on the click of the <h2>
tag. We are also passing the message to be shown as an argument to the alert method.
Let's look at the application controller; the controller needs to provide the data needed by the preceding application
template:
import Ember from 'ember'; export default Ember.ObjectController.extend({ className:"hide", message: "" });
The application controller is present at chapter-3/example5/app/controllers/application.js
Since the application template uses the className
and message
properties in the template, we need to serve them from the controller. So, we have set the className
property to hide
—this CSS class will hide the alert div
. This is done so that initially nothing is visible to the user. The message
property is set to an empty string, as initially there will not be any messages to show in the alert div
.
Now, since we have used the {{action}}
helper to trigger the alert
event, we need to define an event handler for the same in either application controller or application route. As we can see that the application controller does not define any actions object, the event is bubbled up to the application route:
import Ember from 'ember'; export default Ember.Route.extend({ actions:{ alert: function(message){ varapplicationController = this.controllerFor("application"); applicationController.set("className","alert"); applicationController.set("message",message); Ember.run.later(function(){ applicationController.set("className","hide"); },2000); } } });
The application route is present at chapter-3/example5/app/routes/application.js
We need to define the alert
event handler in the actions object of the application route so that when the event bubbles up from the controller to the route, it will be handled in the route.
In the alert
event handler, we set the className
property of the application controller to alert
so that it becomes visible from hidden. We also set the message that we passed through the {{action}}
helper on the controller.
The Ember.run.later
method is an Ember.js equivalent of the setTimeout
method. It respects the Ember.js Run Loop. We will talk more about Ember.js Run Loop in the upcoming chapters.
Now you should be able to understand from the code that we show the alert for 2 seconds, and after that we set the CSS class of the alert to hide
, which will hide the alert
<div>
element from the web page.
The effect of doing this is that when you click on the heading Company Information Brochure, you should see an alert on the top of the page that disappears after 2 seconds, as shown in the following screenshot:
Till now, we have created an alert that gets triggered on the click of a heading tag that is present in the application template. Normally, alerts like these will be triggered after performing some business logic that results in an error. This business logic can be placed in the alert
event handler of the application.
Next, we would want to utilize the event bubbling capability of the framework to trigger this action from anywhere in the application. Let's change the index
template to now trigger the alert
action when anyone clicks on the different columns of the table, as shown in the following:
<table id="t01"> <tr> <th>Company Name</th> <th>Headquarters</th> <th>revenue</th> </tr> {{#each item in model}} <tr> <td {{action "alert" "alert form company name" }}>{{item.name}}</td> <td {{action "alert" "alert form company headquarters" }}>{{item.headquarters}}</td> <td {{action "alert" "alert form revenue" }}>{{item.revenue}}</td> </tr> {{/each}} </table>
The index template of chapter-5/example5 is present at chapter-5/example5/app/templates/index.hbs
As you can see, the template remains the same, with just one exception. We have now added the {{action}}
helper in the <td>
tag of the table. With our design, we now have the ability to pass in different messages from different parts of the application. This results in alert
event being triggered on the click of different columns of the table. Doing this has enabled us to trigger application alert throughout the application in a consistent way.
What if we want to handle the events trigger by the index
template in a different way? For this, we just need to define the index controller and write our business logic in the alert
event handler of the controller, as shown in the following:
import Ember from 'ember'; export default Ember.ObjectController.extend({ actions:{ alert: function(){ //do some controller level processing return true; } } });
The index controller of chapter-5/example5 is present at chapter-5/example5/app/templates/index.js
Now, when we click on any of the columns of the table present in the index
template, we can perform some logic there that is very specific to the index controller.
One very important thing to note here is the return true;
statement at the end of the alert
function. If the event handler returns true, it tells the framework to continue with the event bubbling and the event continues its propagation till the application route. If the alert event handler returns false, this would stop the event propagation and should be used in scenarios where different sections of the page have different alerts requirements.
Ember.js provides us with some useful input helpers to manage text boxes, text areas, checkboxes, and select box.
You could do something like the following:
{{input type="text" value=firstName disabled=nameDisabled size="40"}}
This will render an HTML input box, whose value is bound to the firstName
property in the controller. Similarly, the disabled property of the text box is bound to the nameDisabled
property of the corresponding controller:
<div>Hi Mr. {{firstName}} {{lastName}}</div><br> <div> Last Name is disabled: {{nameDisabled}}<br></div> <br> <div>First Name : {{input value=firstName size=20 }}</div> <div> Last Name : {{input value=lastName disabled=nameDisabled}}</div> <br><br> <div>Enable last name ? {{input type="checkbox" checked=nameDisabled}}</div>
The index template of chapter-3/example6 is present at chapter-3/example6/app/templates/index.hbs
import Ember from 'ember'; export default Ember.ObjectController.extend({ firstName: "", lastName: "Puri", nameDisabled: true });
The index controller of chapter-3/example6 is present at chapter-3/example6/app/controller/index.js
In the preceding example, we have two input types: a text box to accept the first name and the last name, and a checkbox to enable/disable the last name textbox.
You can see in the previous example that the checked attribute of the checkbox and the disabled attribute of last name input box are bound to the same controller property disableName
, which is by default set to true. Doing this would enable or disable the last name text box on checking, unchecking the disable last name?
check box.
Similar to the text box, the text area input helper is self-explanatory and renders an HTML text area:
{{textarea value=longText cols="50" rows="4"}}
This would render a text area, whose value is bound to the longText
property of the controller and has 50 columns and 4 rows.
As seen in the earlier sections, Ember.js provides us with helper methods that enable us to do most common tasks very easily and quickly. But very soon we will end up in situations wherein we start duplicating tasks that were not possible with the default helpers. For example, we want to truncate text to be shown on the page to, say, 10 characters. One way to go about solving this problem would be to add a computed property in our controller, which binds to the long text property and returns the truncated text. This solution is not reusable as it is limited to one controller.
What if we could create our own helper tags that could be used application wide? The Ember.js framework lets you do that very easily. Let's see that by creating a custom truncate helper that will truncate the text passed to it, we would also need to pass in the length after which we truncate the text.
Helpers in Ember CLI projects go inside the app/helpers
directory. The following truncate helper will go inside app/helpers/truncate.js
:
import Ember from "ember"; export default function(value, options) { var length = 40; if(!Ember.isEmpty(options.hash.length)){ length = options.hash.length; } if(!Ember.isEmpty(value)){ if(value.length < length) { return value; } return value.substring(0, length) + "..."; } return ""; };
The truncate helper is present at chapter-3/example7/app/helpers/truncate.js
You can see from the preceding code that we have defined a function that takes in a value
and options
arguments to handle the truncate
helper. The helper method will truncate the text and will also respect any length
attribute that is passed as an argument to the helper.
Ember CLI has two formats of writing the helper; these formats depend on the name of the helper method. Ember CLI encourages the helper names to contain a -
. This helps disambiguate properties from helpers and improves the resolution performance of the framework as it is confident that the names containing -
will be helpers. If the name of the helper contains a -
, then the helper is resolved and will be registered automatically.
So, the above helper could be written as follows:
// app/helpers/trun-cate.js import Ember from "ember"; export default Ember.Handlebars.makeBoundHelper(function(value, options) { // The same logic goes in here });
Please note that if the name contains -
, we export Ember.Handlebars.makeBoundHelper
, instead of just the function. Doing this makes and registers the helper in Ember.js in one go.
When the name of the helper does not contain -
, we will have to explicitly register the helper in app/app.js
using Ember.Handlebars.registerBoundHelper
.
import truncateHelper from './helpers/truncate'; Ember.Handlebars.registerBoundHelper('truncate', truncateHelper);
Here, as you can see, we are using the framework's Ember.Handlebars.registerBoundHelper
to register our helper method. Ember.Handlebars.registerBoundHelper
takes in two arguments: the helper name and a function that will be called when the helper is used.
Now let's see how we can use the helper method that we registered with our application:
{{truncate "This is very very long and beyond" length=20}} <br> {{truncate "this is very long" length=10}}
The truncate method being used in the index template is present at chapter-3/example7/app/templates/index.hbs
<h2>{{truncate "Welcome to Ember.js" length=10}}</h2> {{outlet}}
The truncate helper being used in the application template is present at chapter-3/example7/app/templates/application.hbs
You can see that we can use our truncate Handlebars.js helper across the application, like any other helper method. We can also pass in the length attribute to make it more flexible.
If you run the above chapter-3/example7
using ember serve
and navigate to http://locahost:4200
, you will see the truncated text, as shown in the following:
Like our truncate
helper, you can create other helpers to follow a set of style guidelines across your application. The benefit of this approach is very open-ended and depends on your imagination to bring consistency across your application.
For some users, Handlebars.js may appear to be a very verbose template language as it uses plain HTML whereas users would prefer to use a more concise indentation-based language, something similar to Slim, Jade, Haml, and so on.
Thanks go to Alex Matchneer for creating Emblem.js, which is an indentation-based templating library like Slim, Jade, and Haml. So, if you have worked on any of these libraries before, you should be able to catch up with Emblem.js pretty quickly.
Emblem.js compiles to Handlebars.js and is fully compatible with the built-in Handlebars.js helpers that we have discussed in this chapter.
To use Emblem.js, you need to run the following command in your Ember application directory, which in our case is chapter-3/example8/
:
ember install:addon ember-cli-emblem-hbs-printer emberinstall:npm emblem
This will install the add-on emblem compiler that is compatible with the Ember CLI asset pipeline. More information about the Ember emblem add-on can be found at: https://github.com/201-created/ember-cli-emblem-hbs-printer.
Creating an Emblem.js template is very similar to what we have been doing till now with Handlebars.js, except for the change in the extension of the file. The template files now end with .embl
or .emblem
, instead of .hbs
:
h2 Welcome to Ember.js Application, it uses Emblem.js, the concise indentation based templating library = outlet
The application emblem template is present at chapter-3/example8/app/templates/application.emblem
h3 From Index Template ul each item in model li=item
The index emblem template is present at chapter-3/example8/app/templates/index.emblem
You can see in the above that instead of using plain HTML, we use a different syntax, which is based on indentation. All the elements are rendered based on how you arrange different elements of your template via spaces.
You can see that h3
and the ul
tag are at the same level. Similarly, the each
loop is nested inside the ul
tag. The li
tag is nested within the each
loop block. We use two spaces to nest an element inside its parent element.
The benefit of indentation-based templates is that they are very concise and readable, as you don't have to search for closing HTML tags as you do in plain HTML.
This section gives you a very brief introduction to Emblem.js. We will not be covering the syntax and detailed working of Emblem.js in this book, and it is left to the reader to refer to http://emblemjs.com/ and https://github.com/machty/emblem.js for more information about the framework.
18.188.198.94