March 3, 2015 - No Comments!

Using Ember.Evented to animate a Flash component

Ember.js_Logo_and_MascotLate last year we were working on implementing user-notification flash messages in our EmberJS application, and ran across an excellent blog post describing a service-based approach for flash messages. This was a great start and I really liked the overall approach of using a service paired with flash objects and components, however, when integrating this functionality into our application I ran into some limitations and came up with some workarounds and improvements I wanted to share.

The first thing that I noticed about @sugarpirate_’s flash service was that all the messages appeared and disappeared instantaneously. A more flashy (pun intended) approach would be to use animations on the entrance and exit of each flash message. Let’s start with the entrance animation.

After toying with some jQuery animation approaches, I decided that CSS3 fade in transitions looked smoother and better than javascript-based animations. Conveniently, for any Component, Ember gives you a handy hook (didInsertElement) that triggers when the component is inserted into the document. Bingo! I could use this to remove a class from the Component's element, starting a CSS3 animation.

This is where I hit my first hurdle. With CSS3 transitions getting triggered when the CSS class of a visible element changes, and with didInsertElement actually triggering before the element is visible, then changing the element's class in that callback will result in only seeing the end-state of the animation. The animation itself never appears--it gets skipped because it was never visible in the page.

To solve this problem we needed a callback which would trigger after the changes to the document get rendered. It was not immediately clear how to make this happen since Ember itself does not directly provide a callback for this. However, there is a way to hook into this lifecycle event using Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);. This schedules the function afterRenderEvent to run once in the afterRender portion of Ember’s run loop. This is the perfect place to trigger a CSS3 animation through changing a CSS class on an element. Using this technique we can now trigger CSS3 animations whenever our flash component is inserted - booya! Even with this triumph I admit that I did not find the CSS3 animation to fire consistently until I also added Ember.run.later(function() { elem.removeClass('message--hidden'); } inside my afterRender function. I'm guessing it's something to do with how the browser schedules rendering and not Ember at that point. So what ya gonna do? Get a little messy, that's what.

For the exit animation of my flash messages the jQuery animation slideUp looked pretty good. I quickly noticed that when I removed the flash object from the collection that it immediately gets removed from the DOM and there's no real opportunity to start an animation. Additionally, the example in the blog post observes the willDestroyElement event which I quickly discovered will not actually fire when elements get destroyed! In any case, at that point it’s too late to animate the component’s element anyway - you'd have to clone the element, insert it into the DOM and animate the clone. Eww, dirty - and yet I found examples of people doing just this! Nooooo! There must be a better way!

Enter Ember.Evented - this gives us a way to start a flash removal and communicate back to the component to start an exit animation. We can then communicate to the flash service when the flash component should be done with its exit animation and the component can then be removed from the service’s collection also removing the component from the DOM at the proper time.

The key to putting this together is to add lifecycle events to the flash object. Rather than straight-up destroying or removing the flash object from the collection we call a function on the flash object (we'll call kill()) which triggers a dying event using Ember.Evented's trigger function. The component listens for the dying event, and begins the exit animation. kill() also sets a delayed function to trigger a dead event when the animation completes. To wire it together the service registers a listener for the dead event whenever a flash is created and its callback removes the flash from the collection. This keeps the flash object from having to know about the service, but allows it to trigger removing the flash after the animation completes. On the component side a dying listener is added to the flash object as soon as the component is initialized which begins the exit animation. Ember.Evented is the secret sauce here which gives event pub/sub capabilities to our humble flash object to keep the different objects' responsibilities separated.

Putting it all together there are three main parts:

  1. A FlashObject - representing the individual message and managing the message's lifecycle (dying)
  2. The FlashComponent - the visual representation of the flash and takes care of its animations and interactions.
  3. The FlashService - acts as a factory for FlashObject, and maintains the collection of active FlashObjects
  4. The initializer - ok, I’m not counting this as a 'main part', but we do use an initializer to inject the FlashService into our Controllers and Routes, to let us use them from several different areas of our application.

If you've read this far you must be wondering what this looks like all put-together:

JS Bin

The bones for this flash system all came from @sugarpirate_’s original post on an Ember flash system, but I think putting it all together using Ember.Evented turns it into a much richer system. There's even more that could be done to improve on this, but my hope is this gives you some ideas about how to decouple communication between objects to have them to work together like a boss.

is this an ember bug? note: it appears the reference to this from the original blog post has now been commented-out, so it appears I'm not the only one to notice this behavior.

Published by: Bradley Schaefer in Developers

Leave a Reply