Slack-style loading messages in an Ember app

November 15, 2014 · 4 mins read
Engineering
Alex Robert
Photo by Alex Robert on Unsplash

If you haven’t used Slack already, it’s an awesome chat app for teams that’s similar to IRC. I’ve been using it for a while now, and I’ve pretty much sworn off writing emails for internal communication in favour of Slack.

One little interaction that I particularly enjoy is the random loading message that appears every time you load the app – it’s a nice little touch that makes the app feel just a little bit more human and fun.

Because Ember is well designed for asynchronous routing, it’s really easy to add application loading messages by making use of the little known global loading route that you get for free. Let’s get started!

What We’re Building

What you see when you open the Slack app

We’re going to create a few things:

  • A loading message component
  • A custom computed property macro (CPM) for randomly choosing an item from an array (e.g. Ruby’s Array#sample)
  • CSS for animating the ellipsis

The Component

import Ember from 'ember';
import { sample: computedSample } from '../macros/computed/sample';

var { computed } = Ember;

export default Ember.Component.extend({
  tagName              : 'h1',
  classNames           : [ 'loading', 'animated', 'fadeIn' ],
  showLoadingMessages  : false,
  randomLoadingMessage : computedSample('loadingMessages'),
  loadingText          : 'Loading',

  loadingMessages: [
    'A day without sunshine is like, you know, night.',
    'My fake plants died because I did not pretend to water them.',
    'Weather forecast for tonight: dark.'
  ]
});

The component itself is fairly simple – we’re setting a tag name and some classes (I’m using animate.css to fade the message in), as well as some loading messages and text. In addition, we accept a simple boolean property to show or hide the random loading messages, as sometimes we might only want the ‘Loading…” text to appear. The only thing of interest here is the computedSample CPM, which is defined in a little utility file.

A Custom Computed Property Macro

import Ember from 'ember';

var { computed, getWithDefault } = Ember;

export default function(dependentKey) {
  return (
    computed(`${dependentKey}.@each`, function() {
      var emptyArray = Ember.A([]);
      var items      = getWithDefault(this, dependentKey, emptyArray);
      var length     = items.get('length');
      var randomItem = items[Math.floor(Math.random() * length)];

      return randomItem || '';
    })
  ).volatile();
};

Here, we’re creating a new computed property macro for sampling an array and returning a random item.

What this function does is return an Ember.computed function that’s observing each item in some dependentKey (an array), then accessing a random index on that array. The prototype method volatile tells Ember not to cache the return value, so we get a unique loading message each time the app enters the loading route (even without a hard refresh).

Animating the ellipsis in CSS

i.ellipsis i {
  font-style: normal;
  opacity: 0;
  -webkit-animation: dot 1.3s infinite;
  -webkit-animation-delay: 0s;
  -moz-animation: dot 1.3s infinite;
  -moz-animation-delay: 0s;
  -o-animation: dot 1.3s infinite;
  -o-animation-delay: 0s;
  animation: dot 1.3s infinite;
  animation-delay: 0s;
}

i.ellipsis i+i {
  opacity: 0;
  -webkit-animation: dot 1.3s infinite;
  -webkit-animation-delay: 0.2s;
  -moz-animation: dot 1.3s infinite;
  -moz-animation-delay: 0.2s;
  -o-animation: dot 1.3s infinite;
  -o-animation-delay: 0.2s;
  animation: dot 1.3s infinite;
  animation-delay: 0.2s;
}

i.ellipsis i+i+i {
  opacity: 0;
  -webkit-animation: dot 1.3s infinite;
  -webkit-animation-delay: 0.3s;
  -moz-animation: dot 1.3s infinite;
  -moz-animation-delay: 0.3s;
  -o-animation: dot 1.3s infinite;
  -o-animation-delay: 0.3s;
  animation: dot 1.3s infinite;
  animation-delay: 0.3s;
}

@-webkit-keyframes dot {
  0%, 50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}


@-moz-keyframes dot {
  0%, 50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

@-o-keyframes dot {
  0%, 50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}


@keyframes dot {
  0%, 50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

The above lets us animate the opacity of each individual dot in the ellipsis. Using sibling selectors, we can easily apply a different delay to each dot, and then have the keyframes repeat infinitely.

Using the component

{{loadingText}}
<i class="ellipsis">
  <i>.</i>
  <i>.</i>
  <i>.</i>
</i>

{{#if showLoadingMessages}}
  <small>{{randomLoadingMessage}}</small>
{{/if}}

The template for the component.

<div class="loading-wrapper">
  {{loading-message showLoadingMessages=true}}
</div>

The loading template that’s rendered in the loading route.

The Final Product

The finished product

In Closing

Ember makes front end development such a joy. I hope you’ve enjoyed how easy it is to add Slack-like loading messages into your app!

Discuss on Twitter · Edit this post on GitHub
Lauren Tan

Written by Lauren Tan who lives and works in the Bay Area building useful things. You should follow her on Twitter