Notifications in jide.js

Today we’ll create a number of controls that allow you to add notifications (think of Growl) to your website.

To do that, we need to create two controls: A Notification and a NotificationArea. The Notification will be the actual notification that displays your message to the user, while
the NotificationArea will be an area on the screen where the notifications should be displayed.

A Notification will consist of a few properties:

type
The type of the notification. We’ll support info, warning, error and success by default.
title
The title of the notification.
content
The content that should be displayed in the notification.
icon
An icon that should be displayed next to the content.
closeable
A boolean property that specifies whether or not the notification can be closed by the user.
showTime
An integer that describes how long the notification should be visible before it disappears automatically. Setting it to 0 means that the user has to close it manually.

A NotificationArea is a bit simpler. It only needs an ObservableList of notifications and an edge property that describes where it should be displayed. edge allows five values:

inline
The default setting will keep the notification area where it is.
top-left
Displays the area at the top left corner of the page.
top-right
Displays the area at the top right corner of the page.
bottom-right
Displays the area at the bottom right corner of the page.
bottom-left
Displays the area at the bottom left corner of the page.

We’ll also want a Notifier class that acts as a singleton to add notifications to a global NotificationArea which, by default, will be placed at the top right corner. And, in order to
use nice default icons, we’ll also use a NotificationIcon object that loads some SVG icons from a file.

The NotificationArea

Let’s start with the NotificationArea.

define([
  'jidejs-extra/Class',
  'jidejs/base/ObservableList',
  'jidejs/ui/Control',
  'jidejs/ui/Skin',

  'text!./NotificationArea.html'
], function(Class, ObservableList, Control, Skin, NotificationAreaTemplate) {
  // Define the class
  var NotificationArea = Class({
    $extends: Control,
    $init: function(config) {
      config || (config = {});

      if(!config.children) this.children = new ObservableList();
      else this.children = ObservableList(config.children);
      delete config.children;

      Control.call(this, config);
    },

    edge: Class.Property({ value: 'inline' }),

    // This method should be used to add notifications to the area
    add: function(notification) {
      this.children.insertAt(0, notification);
    }
  });
  NotificationArea.Skin = Skin.create(Skin, {
    template: NotificationAreaTemplate,

    install: function() {
      Skin.prototype.install.call(this);

      var area = this.component;
      area.classList.add('jide-extra-notification-area');
      // Note: We need to dynamically add/remove class names, there is no binding for that
      // kind of task yet so we need to do it here
      this.managed(area.edgeProperty.subscribe(function(event) {
        area.classList.remove('edge-'+event.oldValue);
        area.classList.add('edge-'+event.value);
      }));
      area.classList.add('edge-'+area.edge);

      // registers an event listener for the 'notification:closed' event which is fired by
      // Notification controls that are children of this control.
      // This takes advantage of the event bubbling in jide.js controls.
      this.on({
        'notification:closed': this.handleNotificationClosed.bind(this)
      });
    },

    handleNotificationClosed: function(event) {
      // remove the notification from the list
      var notification = event.source,
        area = this.component,
        index = area.children.indexOf(notification);
      if(index > -1) {
        area.children.removeAt(index);
        event.stopImmediatePropagation();
        event.stopPropagation();
      }
    }
  });
  // Add an enum for the supported edge locations.
  NotificationArea.Edge = {
    INLINE: 'inline',
    TOP_LEFT: 'top-left',
    BOTTOM_LEFT: 'bottom-left',
    BOTTOM_RIGHT: 'bottom-right',
    TOP_RIGHT: 'top-right'
  };

  return NotificationArea;
});

One feature that we’re using here is the ability of jide.js controls to fire bubbling DOM events, i.e. they traverse the DOM tree until their propagation is stopped. Thus, we just listen for the notification:closed event that is fired by notifications whenever they are closed.

The Notification Control

define([
  'jidejs-extra/Class',
  'jidejs/base/Observable',
  'jidejs/ui/Control',
  'jidejs/ui/Skin',

  'text!./Notification.html'
], function(
  Class, Observable,
  Control, Skin,

  NotificationTemplate
) {
  var Property = Class.Property;

  var Notification = Class({
    $extends: Control,

    $init: function(config) {
      Control.call(this, config || {});
    },

    title: Property({ value: '' }),
    content: Property({ value: '' }),
    type: Property({ value: 'info' }),
    icon: Property,
    closeable: Property({ value: true }),
    showTime: 0
  });
  Notification.Skin = Skin.create(Skin, {
    template: NotificationTemplate,
    install: function() {
      var notification = this.component;
      this.isVisible = Observable(true);
      Skin.prototype.install.call(this);
      notification.classList.add('jide-extra-notification');

      // again, we can't add or remove dynamically named classes (sorry!) so we just need to listen for it here
      this.managed(notification.typeProperty.subscribe(function(event) {
        notification.classList.remove(event.oldValue);
        notification.classList.add(event.value);
      }));
      notification.classList.add(notification.type);

      // start a timeout timer if the Notification should only be visible for a specific time
      if(notification.showTime) {
        this.timeoutId = setTimeout(this.closeNotification.bind(this), notification.showTime);
      }
    },

    closeNotification: function() {
      // this is the event that the NotificationArea is listening for!
      this.component.emit('notification:closed');
      this.isVisible.set(false);
      clearTimeout(this.timeoutId);
    }
  });

  return Notification;
});

Okay, as you can see this one is pretty simple, too. Whenever the control is closed, we fire the notification:closed event.

Add some templates

At this point, we’ve already created both controls, all we need is some templates so that jide.js knows how to render these controls.

Let’s start with the NotificationArea again:

<template>
  <div bind="
    foreach: component.children
  "></div>

</template>

Okay, that’s kind of boring, isn’t it? We’re just using a foreach binding here to list all notifications for this area. Now, let’s look at the Notification template:

<template bind="
  css: {
    'is-visible': isVisible
  }
">

  <div>
    <div pseudo="x-close" bind="
      css: {
        'is-visible': component.closeable
      }
    ">

      <svg width="24" height="24" bind="on: { click: closeNotification.bind($item) }">
        <line x1="2" y1="2" x2="22" y2="22" stroke-width="4"/>
        <line x1="2" y1="22" x2="22" y2="2" stroke-width="4"/>
      </svg>
    </div>
    <div pseudo="x-title" bind="text: component.title"></div>
  </div>
  <div>
    <div pseudo="x-icon" bind="content: component.icon"></div>
    <div pseudo="x-content" bind="content: component.content"></div>
  </div>
</template>

The inline SVG contains our close icon, that’s why we’ve added an event listener to it. Everything else about this template is probably old news for you.

At this point, I’d like to spare you the CSS and the SVG for the icons and finish with the Notifier object:

define([
  'jidejs/base/Util',
  'jidejs-extra/control/NotificationArea',
  'jidejs-extra/control/Notification',
  'jidejs-extra/control/NotificationIcon'
], function(_, NotificationArea, Notificiation, NotificationIcon) {
  var exports = {},
    notificationArea = null;

  function initNotificationArea() {
    if(notificationArea === null) {
      notificationArea = new NotificationArea({
        edge: NotificationArea.Edge.TOP_RIGHT
      });
      document.body.appendChild(notificationArea.element);
    }
  }

  var add = exports.add = function(notification) {
    initNotificationArea();
    notificationArea.add(notification);
  };

  var addInfo = exports.addInfo = function(title, content, config) {
    config || (config = {});
    _.defaults(config, { type: 'info', title: title, content: content, showTime: 5000, closeable: true, icon: NotificationIcon.info });
    add(new Notificiation(config));
  };

  var addWarning = exports.addWarning = function(title, content, config) {
    config || (config = {});
    _.defaults(config, { type: 'warning', title: title, content: content, showTime: 5000, closeable: true, icon: NotificationIcon.warning });
    add(new Notificiation(config));
  };

  var addSuccess = exports.addSuccess = function(title, content, config) {
    config || (config = {});
    _.defaults(config, { type: 'success', title: title, content: content, showTime: 5000, closeable: true, icon: NotificationIcon.success });
    add(new Notificiation(config));
  };

  var addError = exports.addError = function(title, content, config) {
    config || (config = {});
    _.defaults(config, { type: 'error', title: title, content: content, showTime: 5000, closeable: true, icon: NotificationIcon.error });
    add(new Notificiation(config));
  };

  Object.defineProperty(exports, 'edge', {
    get: function() {
      initNotificationArea();
      return notificationArea.edge;
    },

    set: function(edge) {
      initNotificationArea();
      notificationArea.edge = edge;
    }
  });

  return exports;
});

As you can see, it contains a bunch of useful methods to help you create new notifications.

Putting it all together

Now let’s take a look at how to use the Notifier:

require([
  'jidejs/ui/layout/VBox',
  'jidejs/ui/control/Button',
  'jidejs-extra/control/Notifier'
], function(VBox, Button, Notifier) {
  Notifier.addInfo('Welcome', 'This demo shows how to create a new note notification.', { showTime: 0 });

  document.body.appendChild(new VBox({
    children: [
      new Button({
        text: 'Notify Info',
        onaction: function() {
          Notifier.addInfo('Info notification', '<p>This notification informs you of something.</p><p>I hope you enjoy it.</p>');
        }
      }),
      new Button({
        text: 'Notify Warning',
        onaction: function() {
          Notifier.addWarning(
            'Warning',
            'You're still looking at a demo!<br>Go download <a href="http://js.jidesoft.com">jide.js</a> right now!'
          );
        }
      }),
      new Button({
        text: '
Notify Error',
        onaction: function() {
          Notifier.addError('
Error notification', 'There has been an error, don't panic!');
        }
      }),
      new Button({
        text: 'Notify Success',
        onaction: function() {
          Notifier.addSuccess('Success', 'Good job!');
        }
      })
    ]
  }).element);
});

We’re using a VBox control to display a set of icons that can be used to create add notifications. Note that we’re using the onaction property in the button configuration parameter
instead of the usual on: { action: function() { /* ... */ } } to save a few characters.

As always, the control is available in the jidejs-extra repository.

Leave a Reply

Your email address will not be published. Required fields are marked *