aaron haurwitz

Do you have things to say? Do you have a message for the world? Are you seeking a vessel to express this divine inspiration? Do you feel like blurting out this message at the top right corner of the screen in a fancy way? Then you may need to consider growl-like notifications in your webapp.

Demo

Before we go any further, I know you greedy webdev types. You're thinking, "demo or it didn't happen." Check it out. When you're done playing, come back and see how it is done.

The approach

At a high level, this is the approach we will take. This is done within the context of an Ember.js webapp, but you could take these ideas and run with them in your framework of choice.

Collection of notification objects

A controller will be responsible for getting told about new notifications and storing them within a collection.

View to house the current notifications

A fixed-position view will be put in the DOM that will have sub-views inserted when new notifications arrive. These sub-views will have to do a little work to figure out where to place themselves and respond to clicks.

Fancy CSS

The least important but most fun. You'll see when we get there.

The controller

For the purposes of this post we'll make this controller the ApplicationController inside of the ember app. This controller is usually generated for you, but if you explicitly define it, it can be whatever you want. Here it is.

I took the liberty of deciding what makes up a notification object. In this example, it contains information about an icon type, a title, and the descriptive message. You may decide you'd want more or less information.

To trigger these notifications throughout your app, you could do it in a variety of ways. Here's one very simple example.

You may have noticed there is a closed property on the notification object. There is a reason this is used instead of recomputing the array of notifications. The view will explain.

The views

The container for your notifications will need to be a fixed-position, high-z-index view that sits empty until populated with notifications. I put this container inside of the application template for demo purposes. It is an extension of the Ember.CollectionView class. The CollectionView will spit out a child view for every item in its content property. Hat tip to Asaf for suggesting this instead of the {{#each}} helper.

I am using the notification object's properties here by inserting the title and the message directly. (Note that the content property of the view is one of the notification objects from the parent view's content array.)

The type is used by a computed property on the view called iconType. This is simply for mapping a meaningful type name to a FontAwesome CSS class. There is also a link with an action on it that will need to close the notification.

Here are all the properties of the NotificationView. We'll regroup after all the code.

Animation

All of the animation is done using the CSS3 transition capability. Examining the styles later you'll see that transitions are done for opacity, top, and right property changes. Keep this in mind when looking back at what styling changes are done by the view.

Automatic hiding

didInsertElement gets called with the view is rendered into the DOM. At that point we set up a setTimeout to emit the close event after 10 seconds, according to the hideAfterMs property.

Fading in and out

When the isOpaque property is true, the view will have the class is-opaque added to its list of classes. It starts off false and is set to true some time shortly after it is inserted into the DOM. This is achieved by Ember.run.later in the didInsertElement hook. If we instead set isOpaque to true immediately in that hook, it acts as though is-opaque was never not there, so no animation occurs.

When the view catches the close event, it sets isOpaque to false, letting the animation happen once again in the opposite direction.

notification.closed property

Whenever a notification is closed, we want all other notifications that were present to stay on the screen, but animate to their new location. To achieve this, we must make sure that the collection of notifications being wrapped by our NotificationContainerView does not change in a significant way. If this collection is rebuilt, every notification view that was in the DOM is ripped out and new ones are put in. We don't want that because the new ones would not know where they were previously and animation would be near impossible.

Instead what we do is set a closed property on any notification that has been closed and just don't display it on the screen anymore. We can simply update the position settings of the views that need to stay visible and they will animate nicely to their new homes.

In the close event handler, we wait 300 milliseconds to set the closed property to true since we have a classname binding on the content.closed property that sets display: none.

Horizontal and vertical placement

Since we are relying on CSS to do the animation for us, we just need to programmatically set the top and right placement of each view. This is done by specifying in the attributeBindings array that we want to have the output of the style computed property slapped into the corresponding attribute of the DOM element. That computed property just does some math to figure out what row and column in which to place the notification, taking the window height into consideration.

Update placement on window resize

When the view is being created (init hook) and destroyed (willDestroyElement hook) we want to bind and unbind a handler to the jQuery window resize event. In this handler, we just want to force the style property to be recomputed. Ember lets us do this through the notifyPropertyChange method. Handy.

CSS

"But it's the beauty on the inside that matters!" Right.

Most important is the transition stuff going on in the .notification class and the .closed class hiding notifications that have been axed.