English |  Español |  Français |  Italiano |  Português |  Русский |  Shqip

Developing an Ember.js Edge

6. Ember.View

Ember pushes HTML onto the page through two mechanisms working together: templates and views. A template is some HTML markup that defines what is to be displayed. Ember templates are written in Handlebars, which look like typical HTML with additional markup to define where properties go, iterating through arrays, and so on. The view is the Ember object responsible for pushing the HTML into the DOM, updating the DOM as the bound object’s values are modified, and then responding to user interaction such as clicks.

Just as Ember.Object is the base class for all Ember objects, all views share a common ancestor as well: Ember.View. Ember.View is also a descendant of Ember.Object and therefore shares its features.

The Basics

Let's take a sample to illustrate how Ember.View works. Note that this is just some example code, and you would rarely ever create view instances manually. In Part Two, we’ll see how everything fits together, but for now, let's learn from the ground up by creating a view manually, starting with creating an instance:

var view = Ember.View.create();

Create a template and compile it through Handlebars:

var template = Ember.Handlebars.compile('<p>testing</p>');

Set the view’s template:

view.set('template', template);

Append the view to document.body:

view.append();

You should see a new paragraph containing the word “testing”. Now let’s spice it up by adding a dynamic property. Here we will display the view’s number property:


var template = Ember.Handlebars.compile(
'<p>{{view.number}}</p>'
);

var view = Ember.View.create({
template: template,
number: 123
});

view.append();

We should now see a new paragraph containing “testing 123” on the page. Let’s see what happens when we change the number property of the view:

view.set('number', 456);

Voila! We changed the number on the view, and the display updated automatically. We did not have to touch the DOM at all, Ember handled all of the display synchronization for us.

Typically, our views will be displaying properties of our models, so let’s give that a shot:


var mustang = Ember.Object.create({
make: 'Ford Mustang',
year: 1968
});

var template = Ember.Handlebars.compile(
'<p>{{view.car.year}} {{view.car.make}}</p>'
);

var view = Ember.View.create({
template: template,
car: mustang
});

view.append();

In this previous example, we created a model and view, and then set the model as a direct property of the view. Recall that Ember objects allow you to bind properties together rather than passing values in directly. Let's try this again using bindings:


App.vette = Ember.Object.create({
make: 'Chevrolet Corvette',
year: 1967
});

var template = Ember.Handlebars.compile(
'<p>{{view.car.year}} {{view.car.make}}</p>'
);

var view = Ember.View.create({
template: template,
carBinding: 'App.vette'
});

view.append();

And of course, if you change one of the properties of the model, the display will be updated automatically:

App.vette.set('year', 1966);

As before, we should see the page update automatically.

Again, this is demonstration code to show how views work. As we mentioned, Ember follows the model-view-controller pattern, has several conventions, and does a lot for you automatically, so you will not have to create views and bind them directly to models. We will see how everything fits together in Part Two.

Using Views in Applications

As we have seen above, templates can be passed directly to the view on instantiation. When the Ember Router is rendering a template (found on the `Ember.TEMPLATES` object or in an `x-handlebars` script tag), it creates a view to manage the rendering of that template. As with other classes in Ember, if a class is not explicitly defined it will be generated.


var App = Ember.Application.create();
App.WelcomeView = Ember.View.extend({
// Override the welcome view here to handle DOM events
// and lifecycle hooks.
});
// App.IndexView will be generated for the index template.
Ember.TEMPLATES['index'] = Ember.Handlebars.compile(
'<section>{{render "welcome"}}</section>'
);
Ember.TEMPLATES['welcome'] = Ember.Handlebars.compile(
'Welcome!'
);

Views only manage DOM and rendering concerns, so the generated classes are often sufficient for simple use cases.

Changing the Template Context

In the preceding examples, all of the properties presented by the template were being referenced through the view. The view had a car property, and the car had other properties. As a result, the properties were being referenced as {{view.car.year}} for example. The view is the context of the other properties. When the full MVC pattern is implemented, however, your properties will typically be within the context of the controller.

Recall in the MVC primer that the data resides in models, the controller acts as a decorator to the model and makes it available to the view, and the view presents the controller’s content in the view. Let's try the same example again, but let's use a controller:


var firebird = Ember.Object.create({
make: 'Pontiac Firebird',
year: 1978
});

var carController = Ember.ObjectController.create({
model: firebird
});

var template = Ember.Handlebars.compile('<p>{{year}} {{make}}</p>');

var view = Ember.View.create({
template: template,
controller: carController
});

view.append();

Now that the view has a controller, we simply reference the properties year and make directly in the template. This is because controller is the default context for a view’s templates.

Now, you may be asking, "But why does year work? Shouldn't it be model.year"? Remember, the controller is a decorator of the model, so every property on the model is directly available on the controller.

DOM Event Handling

Ember views support simple event handling right out of the box. Let’s say you want to show an alert when the user clicks on a view:


var view = Ember.View.create({
template: Ember.Handlebars.compile('<p>Click me</p>'),
click: function(event) {
alert('You clicked me!');
}
});

view.append();

By simply creating an event called click, the view performed the desired functionality when the view was clicked. Ember took care of the hard work of attaching event handlers to the DOM. A comprehensive list of event handlers can be found in the Ember API Docs, but generally they mirror native JavaScript events, i.e.:

  • keyDown
  • keyUp
  • submit
  • change
  • touchStart
  • touchEnd
  • etc.

The jQuery.Event object will be passed in as a parameter to your method. Note that events bubble up through the view hierarchy in exactly the way we’re familiar with. All the normal event bubbling rules apply — including preventing default behaviour and stopping propagation.

Form Helper Views

So far we have seen how Ember objects work, how views bind to other objects to display their properties, and how views handle DOM events. Put them all together, and we'll see how easily we can edit data in our models.

Ember comes with several views for editing properties. Basically, they are just the standard HTML form controls wrapped within an Ember view, but interfacing with them just got much simpler: just bind the model’s property to the view, and Ember takes care of the rest.

For these examples, let’s create a single model from a data hash received from the server:


var data = {
title: 'Black Dog',
artist: 'Led Zeppelin',
isFavorite: true
};

App.song = Ember.Object.create(data);

Ember.TextField

Ember.TextField is the equivalent of <input type="text">. Let’s see a simple example:


var view = Ember.TextField.create({
valueBinding: 'App.song.title'
});

view.append();

Here we created a model, then created an Ember.TextField control and bound its value property to the model's title property. On screen, we’ll see an <input type="text"> control containing the value 'Black Dog'.

Now, we change the value in the control from 'Black Dog' to 'The Ocean'. Because the control’s value is bound to the model’s title property, the property will have been modified to reflect the change:

App.song.get('title'); // => 'The Ocean'

Likewise if we change the value of the model, it will be reflected in the control:

App.song.set('title', 'Whole Lotta Love');

The control now contains the value 'Whole Lotta Love'. All of the plumbing was done for us by Ember, so we can focus on solving business problems and not hand-coding this communication.

Ember.TextArea

Ember.TextArea works just like Ember.TextField, except it is a <textarea> control. Let’s see it in action:


var view = Ember.TextArea.create({
valueBinding: 'App.song.artist'
});

view.append();

We’ll see a <textarea> contains 'Led Zeppelin' rendered to the page. To demonstrate the capabilities of an Ember.View let’s try implementing an auto-expanding text area based on Ember.TextArea:


App.AutoTextArea = Ember.TextArea.extend({

rowsBinding: 'lines',

lines: function() {
var value = this.get('value') || '',
newlines = value.match(/\n/g) || [];

return newlines.length + 1;
}.property('value')

});

var view = App.AutoTextArea.create({
valueBinding: 'App.song.artist'
});

view.append();

We should now see a text area on the page that automatically resizes as it receives input to accommodate the number of lines of text it contains.

What’s wonderful is that our App.AutoTextArea is totally declarative. We just need to declare "rows is the number of newline characters in value plus one," and everything else is taken care of. Reacting to DOM events, reacting to data changes, and updating the rows attribute all takes place automatically and invisibly.

Ember.Checkbox

Ember.Checkbox is the equivalent of, you guessed it, <input type="checkbox">. Just like we bind the value property with Ember.TextField and Ember.TextArea, we bind to the checkbox’s checked property. Here is an example:


var view = Ember.Checkbox.create({
checkedBinding: 'App.song.isFavorite'
});

view.append();

As with the other controls, toggling the control will change the value of the property, and changing the value of the property will change the checked state.

Ember.Select

Ember.Select manages the <select> drop-down control. This control is more complex than the others because it must be given a list of options (in the content property) as well as a value.

Let’s see it in its simplest form:


var options = [
'The Bee Gees',
'Led Zeppelin',
'Vanilla Ice'
];

var view = Ember.Select.create({
content: options,
valueBinding: 'App.song.artist'
});

view.append();

Immediately we can see that "Led Zeppelin" is selected. When we change the selection to "The Bee Gees," App.song.artist instantly changes. And, because our Ember.TextArea is bound to the same property, its value changed immediately too!

Oftentimes though, you will want to show the user one value in the control and then store another value, such as an ID. Let’s try this again, only this time binding to an artistId property. To do so, we still set the content to the array of objects, but we must also specify the optionLabelPath and the optionValuePath. The optionLabelPath specifies the path to the string to be displayed in the list, and the optionValuePath specifies the path to the value, like so:


var beeGees = { id: 1, name: 'The Bee Gees' },
ledZeppelin = { id: 2, name: 'Led Zeppelin' },
vanillaIce = { id: 3, name: 'Vanilla Ice' },
options = [ beeGees, ledZeppelin, vanillaIce ];

var view = Ember.Select.create({
content: options,
valueBinding: 'App.song.artistId',
optionLabelPath: 'content.name',
optionValuePath: 'content.id'
});

view.append();

Also, when the array contains objects, you can access the object directly using the selection property. You can also specify selectionBinding to bind directly to the object.

Here we have demonstrated how to edit a single property value. Put several of these together, and you have a full form that edits your data on the fly. A quick AJAX call to the server to save it, and you're done. Stay tuned for more in Part Two.

Inheriting Views

Remember, while views have a lot of functionality baked in, in the end they are just Ember.Objects, which means they can be sub-classed. In an Ember application, every route has its own custom view class. Sometimes though, you may want to create your own reusable view classes. For example, say you use the alerts in Twitter Bootstrap, and you want to make them "Emberized" by creating an AlertView class and binding to a message property:


App.AlertView = Ember.View.extend({
classNames: ['alert', 'fade', 'in'],
template: Ember.Handlebars.compile('{{view.message}}')
});

var view = App.AlertView.create({
message: 'Wow, that was easy!'
});

view.append();

Let’s take it a step further and add a close button. We will do this by reopening the class to change the template:


App.AlertView.reopen({
template: Ember.Handlebars.compile(
'{{view.message}}<a class="close" data-dismiss="alert">&times;</a>'
)
});

var view = App.AlertView.create({
message: 'Close me!'
});

view.append();

The close button is now there, and clicking it will close the alert.

View Lifecycle Hooks

Sometimes we may create view classes that require initialization when in the DOM. For instance, many jQueryUI controls such as the accordion require you to call a function on the element before it actually looks like the control. This is where Ember.View event hooks come into play.

Ember provides a series of event hooks which can be overridden in a view class to hook into the DOM lifecycle of a view. These hooks are:

  • willInsertElement: Called when a view is going to insert an element into the DOM.
  • didInsertElement: Called when the element of the view has been inserted into the DOM or after the view was re-rendered.
  • willClearRender: Called when the view is about to rerender, but before anything has been torn down.
  • willDestroyElement: Called when the element of the view is going to be destroyed.
  • parentViewDidChange: Called when the parentView property has changed.

For the example of jQuery plugins, we’d mostly rely on didInsertElement and willDestroyElement to perform any plugin setup or teardown.

willClearRender can sometimes be a better point for tearing-down custom observers, and parentViewDidChange may be useful for such tasks as telling plugins to resize to a new containing element.

Let’s get these features out by creating a view that fades in when inserted. First, the view must be hidden from the start, which the .hide class from Bootstrap provides. Then we will add an event handler for didInsertElement to animate the element:


App.AnimationView = Ember.View.extend({
classNames: ['hide'], // From Bootstrap

didInsertElement: function() {
this.$().fadeIn();
}
});

var view = App.AnimationView.create({
template: Ember.Handlebars.compile('<p>Watch me fade in...</p>')
});

view.append();

Notice in the didInsertElement function that we call this.$(). The context of this function (this) is the view instance, and the $ function is a jQuery object scoped to the view’s element. Any time we need to access jQuery or the DOM itself for a view (which should not be very often), we can do so by calling the view.$. We can also pass in a jQuery selector as the parameter, such as this.$('.alert'), which will return all descendant elements with the .alert CSS class.

Ember.ContainerView

Though we’ll not often find ourselves creating views programatically, it’s also worth knowing about Ember.ContainerView. Used heavily internally by Ember, container views provide an array-like interface for managing child views:


var container = Ember.ContainerView.create({
childViews: ['mainView', 'sidebarView'],
mainView: Ember.View.create(),
sidebarView: Ember.View.create()
});

The order of child elements in the DOM will match the order of childViews. Child views can be rearranged with the normal array manipulation methods:


var alertView = Ember.View.create({
template: Ember.Handlebars.compile('Alert!')
});
container.addObject(alertView);
Ember.run.later(function() {
container.removeObject(alertView);
}, 1000);

As these operations are performed, the corresponding elements are instantly added and removed from the DOM along with all of the expected lifecycle hooks.

Ember.ContainerView is the base on which specialized subclasses Ember.CollectionView and Ember.EachView are built. Ember.EachView is the underlying mechanism for the bound {{#each}} helper we’ll get to know later on in the book.

Wrapping Up

`Ember.View` manages DOM lifecycle and events, but is not responsible for decorating models or managing state. In the next chapter, we will learn about how Ember presents models to templates and handles actions with controllers.

There has been error in communication with Booktype server. Not sure right now where is the problem.

You should refresh this page.