Add a Promotional Drawer and Notification Icon

I don't know about you, but I occasionally visit ecommerce sites with my developer hat on, rather than my shopper one. It's good to take a look at a wide range of sites and get a sense of what various brands are doing in order to increase conversions, improve user experience and promote their inventory.

On one such internet excursion, I was taking a look at the Old Navy site and I was immediately hit by a bit of functionality they were obviously very pleased about:

I'm not familiar with this sort of user experience element; I mean, looking at the code, they call it a 'promo drawer' and I suppose that's a good enough name for it.

On the face of it, it's a very conspicuous item of functionality: as soon as you visit the site, not only are you hit with a modal asking you to sign up to the newsletter but the promo drawer is automatically opened to show what lays inside. Personally, I'm annoyed by this — regardless of which hat I'm wearing — but there is certainly promise in this.

After all, it's been commonplace in ecommerce for over five years to have a promo strip across the top of your site. I mean, this simple trend of just having a little snippets of text about promotions on the site has endured: we include it in SCA and it's still common across many ecommerce sites.

I'm all about being subtle with design: I advocate not trying to overwhelm the shopper, especially as soon as they visit. There's also a difference between the show-don't-tell awe of, say, the billboards in Times Square, and an over-eager assistant who can't wait to tell you about today's offers as soon as you enter a store. In other words, if 'subtle' isn't your thing then you at least need to let whatever it is speak for itself.

Once you dismiss the drawer (and the newlsetter modal) you are greeted by the homepage and with it all the standard things you'd expect. The handle for the promo drawer is permanently fixed to the bottom of the browser window — a conspicuous and constant reminder that the offers are always there; the text used is also a description of an offer (rather than a generic 'click here' type message).

However, another thing which is also interesting about it is in the top right corner of the page. In a space typically reserved for the most important 'quick links' they've put a notification-like icon:

When you click it, where does it take you? To the promo drawer of course! Another thing that happens is that a cookie is saved; when hasSeenPromoDrawer is set to true, the eye-catching red number is no longer shown.

Clearly, the intention here is to replicate the subliminal urgency of seeing unread notifications. As most — if not all — of us now have a smart phone, we're all familiar with seeing icons with colored bubbles over them, informing us of the next super urgent thing that requires our attention. Well, Old Navy have kinda co-opted that.

In my opinion, it's extremely rare that a marketing message ever requires a user's immediate attention, but I wouldn't go so far as to say that it's a dark design pattern. It is, however, undeniably a good way to direct their attention to promotions, even if it does functionally duplicate what the promo handle does.

From here we can start to plan out how we might implement similar functionality in SuiteCommerce Advanced. By the end of the article we should have a good framework in place for you to build on:

Before You Start: Prepare the Module

As always, we start by creating the directory structure. I'm going with:

  • PromoDrawer@1.0.0
    • Configuration
    • JavaScript
    • Sass
    • Templates

Create an ns.package.json file in the root of the module's directory and put in:

{
  "gulp":
  {
    "configuration": ["Configuration/*.json"]
  , "javascript": ["JavaScript/*.js"]
  , "templates": ["Templates/*.tpl"]
  , "sass": ["Sass/*.scss"]
  }
}

We then need to update distro.json:

  1. Register the new module
  2. Add the module to shopping.js and myaccount.js
  3. Add the module to shopping.css and myaccount.css

Finally, create an entry point file for the module. In JavaScript, create PromoDrawer.js:

define('PromoDrawer',
[

], function
(

)
{
  'use strict';

  return {
    mountToApp: function(application)
    {
      console.log('PromoDrawer.js loaded')
    }
  }
});

Save and deploy and then load your homepage. You should see your message in the developer console.

Extend the Header

While thinking about this, I wondered what the best place was for this functionality to live. I think a good module to extend would be the header. We need something that will be present throughout the shopping and account applications, but not the checkout, and we also need add in the notification icon. The header view is already a composite view so it's relatively easy to add new child views to it.

Before we do that, however, we need to prep our two new views and templates.

Create PromoDrawer.Drawer.View.js and PromoDrawer.Notification.View.js in the JavaScript folder, and then create promodrawer_drawer.tpl and promodrawer_notification.tpl in the Templates folder.

In each of the templates, just put some placeholder text so we'll know each one has loaded correctly.

Now build up each of the new views with enough bones so that they hold together and load their respective templates. For example:

define('PromoDrawer.Drawer.View'
, [
    'Backbone'
  , 'promodrawer_drawer.tpl'
  ]
, function
  (
    Backbone
  , promodrawerDrawerTpl
  )
{
  'use strict';

  return Backbone.View.extend({
    template: promodrawerDrawerTpl
  });
});

Now what's left to do to get this test working is to extend the header view. As I said, it's already a composite view, so it's just a case of adding in some additional ones.

Head back to PromoDrawer.js and:

  1. Add the header, drawer and notification views as dependencies
  2. Remove the existing mountToApp code
  3. Extend the childViews object in the header view twice, adding in the new views

Now, this'll render the views but you'll note that we haven't actually told the code to render them anywhere. We, of course, want to render them in the header template — anywhere within here will do at the moment. For this there are two options: override the template, or inject the code using the plugin container.

I'm a fan of the plugin container, and I've recommended it before and I think it's a good fit here: we don't want to change any of the existing content within the template, we just want to add in new stuff.

Still in PromoDrawer.js, add PluginContainer as a dependency and then install plugins to inject HTML with each data-view parameter on them.

Thus, your final code will look something like this:

define('PromoDrawer',
[
  'Header.View'
, 'PromoDrawer.Drawer.View'
, 'PromoDrawer.Notification.View'
, 'PluginContainer'
], function
(
  HeaderView
, DrawerView
, NotificationView
, PluginContainer
)
{
  'use strict';

  HeaderView.prototype.preRenderPlugins = HeaderView.prototype.preRenderPlugins || new PluginContainer();

  HeaderView.prototype.preRenderPlugins.install
  ({
    name: 'PromoDrawerDrawer'
  , execute: function ($el, view)
    {
      $el
        .find('.header-subheader')
        .before('<div class="promodrawer-drawer" data-view="PromoDrawerDrawer"></div>');
      return $el
    }
  });

  HeaderView.prototype.preRenderPlugins.install
  ({
    name: 'PromoDrawerNotification'
  , execute: function ($el, view)
    {
      $el
        .find('.header-menu-cart')
        .after('<div class="promodrawer-notification" data-view="PromoDrawerNotification"></div>');
      return $el
    }
  });

  HeaderView.prototype.childViews.PromoDrawerDrawer = function()
  {
    return new DrawerView
  };

  HeaderView.prototype.childViews.PromoDrawerNotification = function()
  {
    return new NotificationView
  };
});

Save and run your local server. When you hit up your local homepage, you should see something like this in your header:

From here we can start to develop the functionality.

Build the Drawer

Now, a bit of text in the header is hardly what we want. Let's work on creating the UI element itself.

I anticipate that a lot of this stuff is going to be Sass and JS (jQuery) based work that works on my site but may not directly translate onto your site. However, I think there are a few key design elements we can work on that will carry over their meaning.

The Template

Let's start by taking a look at some basic CSS. There are a few obvious requirements that we covered earlier on:

  1. The drawer must be fixed to the bottom of the page
  2. The drawer must have a handle, which pulls open the drawer when clicked

So let's build up the drawer template a little bit. The code we wrote invoking the plugin container is already wrapping the template in a container, so we can add on the handle and content area. So, something simple to get started on looks like this:

<div class="promodrawer-drawer-handle">
    <p>Check out these great offers!</p>
</div>
<div class="promodrawer-drawer-content">
    <div class="promodrawer-drawer-content-block1">
        <p>1</p>
    </div>
    <div class="promodrawer-drawer-content-block2">
        <p>2</p>
    </div>
    <div class="promodrawer-drawer-content-block3">
        <p>3</p>
    </div>
</div>

This has created two main areas: one for the handle and one for content, with the latter segmented into (arbitrarily) three blocks. Next up, we need to style this so it works as we intend it to.

The Sass

Create _promodrawer.scss in Sass and put the following in it:

.promodrawer-drawer {
    position: fixed;
    bottom: -275px;
    z-index: 9000;
    height: 300px;
    width: 100%;
    cursor: pointer;
    transition: ease-in 500ms;
    text-align: center;
}

.promodrawer-drawer.open {
    bottom: 0;
}

.promodrawer-drawer-handle {
    margin: 0 auto;
    max-width: 300px;
    background: $sc-color-primary;
}

.promodrawer-drawer-content {
    white-space: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
}

.promodrawer-drawer-content-block {
    display: inline-block;
    height: 265px;
    width: 400px;
    padding: $sc-base-padding * 3;
}

.promodrawer-drawer-content-block1 {
    @extend .promodrawer-drawer-content-block;
    background: $sc-color-theme;
}

.promodrawer-drawer-content-block2 {
    @extend .promodrawer-drawer-content-block;
    background: $sc-color-theme-light;
}

.promodrawer-drawer-content-block3 {
    @extend .promodrawer-drawer-content-block;
    background: $sc-color-theme-background;
}

So here are some thoughts about why I chose to style it this way:

  1. We're using fixed positioning: specifying exactly where we want the drawer to be. By default it'll be pushed off the page, but we've got a supplementary class (.open) which we'll use to move it up and down.
  2. The content area class has properties that deal with overflow. This is important as you'll likely want your promo blocks to be a standard size: so what happens when they exceed the space? Well, they merely roll over off the side of the page: PC users will see a scrollbar, touchscreen users can just swipe across.
  3. The rest of the styling covers what the blocks will look like: this is obviously up to you. I've just added some colors so they are distinguishable.

Save, and restart your local server. Refresh your local homepage and you should see the handle peaking out of the bottom of the page. As we don't currently have a mechanism to trigger the opening of the drawer, you'll need to inspect the element and add in the appropriate class to open in it. When you've done that, you should see something like this:

From here you can customize it and set it up as you like. For now, let's move on to some JavaScript.

The JavaScript

The key part of the drawer's JavaScript is toggling the class that opens the drawer. For that we can use jQuery.

Head back to PromoDrawer.Drawer.View.js and add jQuery as a dependency. Then, in the return statement, add in:

, events:
  {
    'click .promodrawer-drawer-handle' : 'toggleDrawer'
  }

, toggleDrawer: function()
  {
    jQuery('.promodrawer-drawer').toggleClass('open');
  }

If you read my article on getting to grips with Backbone events then you'll recognize what's going on here. We're effectively creating a listener for when a user clicks on the handle and then telling it to call a method. That method is one line of jQuery to toggle the class we need.

If you save the file and then refresh the homepage, then clicking the handle will cause it to open and close. Awesome!

Build the Notification Icon

Let's have a think about what we need to do:

  1. Show an icon — and have that icon trigger the drawer when clicked
  2. Show a notifier on the icon when the shopper has 'unread' promotions
  3. Toggle the drawer when the icon is clicked

The Template and Sass

We've talked about icons before, when we went through adding our own by extending Font Awesome. For the purposes of this tutorial, we're going to use an existing icon. Because we're going for a notification theme, I'm going to use the envelope icon but you can take a look at the full list and choose your own, if you like. Just note that your version of Font Awesome might be a little behind the latest; for example, it would be cool to have the icon change to the open envelope when clicked, but that's not available in 4.2.0.

To use the Font Awesome icon Sass, we just build up a template mirroring what we do throughout the rest of the application. For example, if you want to get a sense of this in core code, take a look at the header templates (eg header.tpl).

Replace the contents of promodrawer_notification.tpl with:

<div class="promodrawer-notification">
    <a class="promodrawer-notification-link" href="#">
        <i class="promodrawer-notification-icon {{#if unreadNotifications}}unread{{/if}}"></i>
    </a>
</div>

The key point is that we have i elements, which we then attach a class to. When we set this class in our Sass, we're going to tell it to extend a Font Awesome class which uses the content property to inject the icon we want. Personally, I'm going to use the envelope icon.

You can find a list of icon classes in third_parties > font-awesome > scss > _icons.scss. Most of them, however, are pretty obvious — they match the names FA uses.

Thus, in _promodrawer.scss, put:

.promodrawer-notification-link {
    @extend %header-link;
}

.promodrawer-notification-icon {
    @extend .fa;
    @extend %fa-envelope;
    color: $sc-color-secondary;
    font-size: $sc-h3-font-size;
    line-height: $sc-target-size;
    min-width: $sc-target-size;
}

.promodrawer-notification-icon.unread {
    color: $sc-color-primary;
}

As mentioned, we already have other icons in the header, so we can piggyback of the class we use on their containers.

For the icon itself, we can borrow the styling we use for other icons while extending the specific icon class we need.

Then, finally, the way I'm marking up unread notifications is to change it's color from the secondary color to the primary color. Admittedly, this will clash a little with the shopping cart icon, which also uses this color, but you get the idea. If you're feeling fancy, you could make a number appear next to it or perhaps use a subtle animation.

If you refresh your local homepage, you should see your new icon appear. If you don't see the icon (or you see a square instead) then it's possible that you need to clear the local distribution folder and rerun the gulp task for fonts. This is because the gulp task for fonts isn't 'watched' the same way that we do for templates and Sass. So, quit your local server and then run:

gulp clean && gulp fonts && gulp local

When that's done, you should see the icon appear.

The JavaScript and Cookie

Now we just need to make it functional. There are three parts to this:

  1. A cookie that stores whether the shopper has seen the notifications already
  2. JavaScript that triggers a class on the icon depending on the value of this cookie
  3. JavaScript that functions like the code in the other view (ie opens and closes it)

First: the cookie. Unsurprisingly, we've covered cookies before, so let's jump right in.

Replace the contents of PromoDrawer.Notification.View.js with:

define('PromoDrawer.Notification.View'
, [
    'Backbone'
  , 'jQuery'
  , 'promodrawer_notification.tpl'
  ]
, function
  (
    Backbone
  , jQuery
  , promodrawerNotificationTpl
  )
{
  'use strict';

  return Backbone.View.extend({
    template: promodrawerNotificationTpl

  , events:
    {
      'click .promodrawer-notification-icon': 'toggleDrawer'
    }

  , toggleDrawer: function()
    {
      jQuery('.promodrawer-drawer').toggleClass('open');
      jQuery.cookie('readPromoNotifications1', true, {expires: 30, path: '/' });
      jQuery('.promodrawer-notification-icon').removeClass('unread');
    }

  , getContext: function()
    {
      return {
        unreadNotifications: !jQuery.cookie('readPromoNotifications1')
      }
    }
  });
});

It reads pretty much the same as the other view (which we'll need to change too). The key differences relate to the use of the cookie: when the icon is clicked, it now not only triggers the class on the drawer, but also sets a cookie and removes the .unread class on the icon (if present). We also have a setting in the context object, which we mentioned in our template, that determines whether to render this class in the first place.

If you refresh your homepage, you should see the notification icon glowing in your site's primary color (indicating unread messages). Now, if you click the icon, it will toggle the drawer and set the cookie; refresh the page again and the icon will be the 'read' color.

Now all that's left for you to do is update the code in PromoDrawer.Drawer.View.js so that it also sets the cookie and removes the class. This is easy enough: just copy and paste the toggleDrawer code.

Final Thoughts

That's it — you have the barebones code, realising what I think is pretty neat functionality. Go forth and set it up to your heart's content.

You'll notice, I've not set any configuration code: this is up to you. I envision using the configuration tool to pull in details of the promotions from the backend, so that your marketing managers can set the contents of the blocks themselves. That'll be cool.

There is a small issue with the cookie and notification icon, if you're going that way, though. Note here I've put a number at the end of the cookie: the reason being that this may be the simplest way to notify users when there are new promo messages. In other words, when you put new promos in you can increment the number and create a new cookie.

My code configures the cookies to expire after 30 days, which is refreshed every time the shopper toggles the drawer. You could be more sophisticated than this. One way would be to check before setting the cookie, to see if it already exists or if its age is of a certain value; this might be useful if you push out updated promo messages at regular intervals.

An even more sophisticated way involves the configuration tool and assigning each message a unique ID. When the marketing manager sets a new promo message, they set the ID, which is then pulled down along with the content and put into an array. When the user reads those messages, their IDs are stored in the cookie. You could write code that compares the two arrays of IDs — when a new one from the server is detected, it's flagged and updates a count of unread messages. You can then use that value to trigger the unread status. If you're interested in this, you could take a look at the similar functionality used by recently viewed items in RecentlyViewedItems.Collection.js.

Finally, don't forget that you can apply promo codes via a URL parameter — so one of your blocks could be an easy "click here to apply free shipping" type promo. Similarly, you could redirect a user to a selection of jackets which also applies a discount to them. Get creative.

If you want my code to compare yours against, it's available here: PromoDrawer@1.0.0.zip.