Partager via


Chapter 10: Application Notifications

Introduction | Notifying the User - Where Notification Messages are Displayed, How Notification Messages are Initiated, How Individual or Multiple Notification Messages are Displayed, User Session Timeout Notification | Website Error Notification | Prompting Users | Desktop Notifications - Implementing Pinned Sites, Pinned Sites in Mileage Stats, Feature Detection, Enabling Website Pinning, Dynamic Jump List Updating and Notification Icons, Requirement for Jump List Items to Appear | Summary | Further Reading

Introduction

All web applications that users consider responsive have one thing in common: they provide appropriate and timely feedback to the user. This feedback can come in many forms, including a save or success message following a completed task, subtle animations in response to a user interface (UI) gesture, a progress message for long-running tasks or input error messages displayed before a page is submitted.

How the application displays notifications to the user is almost as important as the information itself. Intrusive message boxes, modal dialogs, and overlays (floating messages) that require the user to dismiss messages, can interrupt the user's workflow, get in the way, and degrade the overall user experience.

In addition to providing feedback during normal use, the website must also provide quality feedback when a non-recoverable error occurs. Quality feedback means providing understandable information about what has occurred, along with clear instructions on how to proceed.

In this chapter you will learn:

  • How to provide unobtrusive user notification messages.
  • How to handle multiple simultaneous notification messages raised by the application.
  • The benefits of encapsulating the display and management of user notifications in a single JavaScript object.
  • How to display a helpful global error page.
  • How to set up a global error handler for Ajax requests.
  • Alternatives to modal dialogs for prompting users.
  • How to enable application notifications on the desktop with the Pinned Sites API.

The technologies discussed in this chapter are jQuery UI Widgets and Pinned Sites in Windows® Internet Explorer® 9.

For a comprehensive look at input validation error messages, see Chapter 11, "Server-Side Implementation."

Notifying the User

Providing a high-quality application notification experience requires careful planning that emphasizes where notifications will be displayed, what events initiate a message, how potential multiple simultaneous messages will be handled, and how to decouple the message originator from the object that displays the message.

During the design phase of the Mileage Stats application, the Project Silk team discussed where and how notification messages would be displayed. We spent time prototyping several different notification designs.

Where notification messages are displayed is an essential part of the overall application user experience (UX) and UI design. Our initial design called for messages and progress bars to be displayed within the boundaries of each jQuery UI widget. After building several prototypes and performing usability testing, the team determined that this design was unnecessary because the UI loaded quickly, eliminating the need for a loading progress bar. The team decided that displaying user messages in a single location made for a much better experience than having messages displayed within individual widgets.

During the development cycle, the team relied on usability testing to refine their approach to generating user messages. Initially, the team displayed messages each time an Ajax request was invoked. This caused the UI to be too busy; we then used a time delay so that the message would only display if the request took longer than the delay. This too got messy, requiring a good deal of code that added little or no value to the application. In the end, the "less is more" principle triumphed, resulting in a good balance of informative messages.

Interactive and engaging applications such as Mileage Stats can execute multiple asynchronous operations. For example, the Dashboard page loads data for several jQuery UI widgets in addition to the chart widget. Each of these operations loads data for a region of the UI. Any of these operations is a potential point of failure requiring an error message. It's important that the application's notification implementation be able to manage multiple simultaneous or nearly simultaneous messages.

From an architectural design perspective, it's critical that message initiators not be responsible for determining how to coordinate the display of messages in the UI. Decoupling the message initiator from the rendering object allows both of them to evolve independently and to be tested in isolation.

This section gave a glimpse into how the team worked together to maintain the delicate balance of UX, UI, and engineering concerns. It's this type of designer-developer collaboration that enabled the team to deliver a successful notification feature.

Where Notification Messages are Displayed

Mileage Stats is composed of widgets. The decision to create and use a widget for displaying notification messages is a natural architectural choice for this application. Widgets have flexible and powerful UI capabilities, provide for encapsulation of behavior, and can have external dependencies like the publish and subscribe (pub/sub) JavaScript object injected into their options object during creation.

Mileage Stats uses a single widget called status for displaying messages to the user. The status widget subscribes to the Mileage Stats status pub/sub message. It also handles the placement and rendering of messages as well as the coordination of multiple simultaneous messages.

Location of the status widget

Hh404084.e7fcb947-0c7c-4f42-849c-6f9ee1614ee2(en-us,PandP.10).png

The status widget is rendered within the header widget UI, as pictured above. This top, semi-central location was chosen because it's easier for the user to notice the message in this location, as opposed to a message area along the bottom of the browser window. The balance of easily noticed, easy-to-read, yet unobtrusive user notifications took time, patience, and usability testing, but the multiple design iterations were worth the extra investment of effort.

How Notification Messages are Initiated

Mileage Stats notification messages are initiated by widgets and communicated to the status widget using the pub/sub JavaScript object. Like other pub/sub messages, the status message has an associated payload object that is passed with the message.

Notification messages passed using Pub/Sub

Hh404084.33c19ead-c987-4fa8-9951-5ff578fd8e3c(en-us,PandP.10).png

The code snippet below is from the vehicleDetails widget. The _publishStatus method is responsible for making the pub/sub call. It's called internally by other widget methods to initiate the display of a message. The status argument is the message payload and is forwarded in the publish call. The publish method was passed in the widget options object when the widget was created and points to the pubsub JavaScript object. The jQuery isFunction method verifies that publish is a valid JavaScript function object before it's called.

// Contained in mstats.vehicle-details.js
_publishStatus: function (status) {
    this.options.publish(mstats.events.status, status);
},

As stated earlier, Mileage Stats does not bother the user with data request messages. However, when initiating an Ajax operation such as a save or delete, it's important to keep the user informed by updating the UI as the request proceeds and concludes.

The following functions show how easy it is to initiate the display of a user message:

  • The _showDeletingMessage function is called after the user confirms his or her intent to delete the vehicle. This message is intended to inform the user that the vehicle deletion has been submitted to the server.
  • The _showDeletedMessage function is called after a successful deletion of the vehicle, informing the user that the deletion was successful.
  • The _showDeleteErrorMessage function is called if an error occurred while deleting the vehicle.
// Contained in mstats.vehicle-details.js
_showDeletingMessage: function () {
  this._publishStatus({
    type: 'saving',
    message: 'Deleting the selected vehicle ...',
    duration: 5000
  });
},
_showDeletedMessage: function () {
  this._publishStatus({
    type: 'saved',
    message: 'Vehicle deleted.',
    duration: 5000
  });
},
_showDeleteErrorMessage: function () {
  this._publishStatus({
    type: 'saveError',
    message: 'An error occurred deleting the selected vehicle. Please try again.',
    duration: 10000
  });
}

Each function creates an object literal containing a type, message, and duration property. The type property is used by the status widget to prioritize multiple or overlapping display message requests. The message is the text of the message to display and the duration is how long the message should display.

For detailed information on the inner workings of the Mileage Stats pub/sub implementation, see Chapter 8, "Communication."

How Individual or Multiple Notification Messages are Displayed

In the following _create method, the status widget subscribes to the status event. When this event is raised, the _statusSubscription method is invoked.

The _statusSubscription method is responsible for displaying and hiding messages as well as managing multiple simultaneous messages. If a message is being displayed and another message with a higher priority arrives, the higher priority message will be shown.

// Contained in mstats.status.js
_create: function () {
  // handle global status events
  this.options.subscribe(mstats.events.status, this._statusSubscription, this);
},

...
_statusSubscription: function (status) {
  var that = this,
            current = this.currentStatus;

  status.priority = this._getPriority(status);

  // cancel displaying the current message if its priority is lower than
  // the new message. (the lower the int the higher priority)
  if (current && (status.priority < current.priority)) {
    clearTimeout(current.timer);
  }

  current = status;

  this.element.text(status.message).show();

  // set the message for the duration
  current.timer = setTimeout(function () {
    that.element.fadeOut();
    that.currentStatus = null;
  }, status.duration || this.options.duration);
},

_getPriority: function (status) {
  return priorities[status.type];
},

User Session Timeout Notification

Mileage Stats uses forms authentication, with a session time-out threshold of 20 minutes. If the session has timed out, the request (Ajax or non-Ajax) is redirected to the page specified by the forms authentication loginUrl in the web.config file.

In traditional websites that perform page reloads between pages, it's common to redirect the user to a sign-in page when their session times out. Applications like Mileage Stats that make heavy use of Ajax calls to retrieve data perform few full-page reloads. Consequently, if a session timeout occurs, it's usually during an Ajax request. Let's examine what happens when an Ajax request is redirected because of an authentication session timeout:

  1. Ajax JavaScript Object Notation (JSON) data request initiated.
  2. Forms authentication runtime detects an expired session and redirects the request to the sign-in page.
  3. A parsing error occurs because the Ajax handler is expecting JSON data and not HTML. The HTML is the content of the sign-in page to which the request was redirected.
  4. An Ajax error callback is invoked.
  5. A global Ajax error callback is invoked.

Errors that can occur anywhere in the application can often be handled in a centralized location so that individual objects don't need to repeat the same error handling code. Mileage Stats implements the global ajaxError method handler shown below to catch errors occurring during an Ajax request. In Mileage Stats, the primary purpose of this method is to identify whether the initiating Ajax request caused a session time-out error and, if so, to redirect the user to the sign-in page.

When looking at the code below, "jqXHR.status === 200" appears out of place or incorrect. Remember, this method is only executed when an Ajax error occurs. If the session times out and the request is redirected to the sign-in page, the response status code will be 200 because the redirect succeeded. In addition to checking for the response status code, this method also verifies that the returned HTML contains the sign-in page's title. If both conditions are met, the browser is redirected to the sign-in page.

// Contained in mileagestats.js
$(document).ajaxError(function (ev, jqXHR, settings, errorThrown) {
    if (jqXHR.status === 200) {
        if (jqXHR.responseText.indexOf('Mileage Stats Sign In') !== -1) {
        window.location.replace(mstats.getRelativeEndpointUrl('/Auth/SignIn'));
        } else if (jqXHR.responseText.indexOf('Mileage Stats | Accident!') !== -1) {
        window.location.replace(mstats.getRelativeEndpointUrl('/GenericError.htm'));
        }
    }
});

Note

If the originating Ajax calling code also implements an error handler, the originating Ajax caller's error handler will be called first, and then the above global Ajax error handler will be called.

Website Error Notification

ASP.NET allows you to specify a default error page that the ASP.NET runtime will redirect users to when an unhandled exception occurs. This error page is configured in the web.config file's customErrors section.

// Contained in web.config
<customErrors defaultRedirect="GenericError.htm" mode="RemoteOnly" />

The error page should look and feel like it is part of the website, contain a brief explanation of why the user has been redirected to this page, and provide links that allow a user to continue using the site.

Mileage Stats GenericError.htm page

Hh404084.ccf1aa91-d5cb-4b36-b9f0-685fda520969(en-us,PandP.10).png

Prompting Users

During the design phase of Project Silk, the team had a goal of not prompting users with modal dialogs. Website UX designers are getting away from modal dialogs that ask the user questions like, "Are you sure?" Instead, designers prefer an undo feature that allows users to undo the previous task. The undo feature also enhances the application by extending undo capabilities to tasks that did not require a confirmation dialog. Because Mileage Stats is only a sample application, it has limited functionality. The team wanted to implement the undo feature, but other features took priority. A production application could include it.

The following code uses the JavaScript confirm function to validate the user's request to fulfill a maintenance reminder.

// Contained in mstats.reminders.js
fulfillReminder: function (fulfillmentUrl) {
    var shouldfulfill = confirm('Are you sure you want to fulfill this reminder?');
    if (shouldfulfill) {
        this._fulfillReminder(fulfillmentUrl);
    }
},

Note

The jQuery UI dialog provides an alternative to using the JavaScript confirm dialog. If you are leveraging jQuery UI plug-ins, you should consider using the jQuery UI dialog for consistency in your UI.

Desktop Notifications

Given that modern web applications can provide excellent user experiences that rival desktop applications, the team wanted to take the next logical step and integrate the Mileage Stats application with the user's desktop to provide dynamic user notifications. This integration was made possible by the Internet Explorer 9 Pinned Site API.

Websites that implement the Pinned Site API can feel more like a native Windows application. They take advantage of the Microsoft® Windows® 7 taskbar capabilities and, when launched, the browser window is customized specifically for the website. The full Pinned Sites experience requires Internet Explorer 9 running on Windows 7. Windows Vista® has a reduced experience that provides: site pinning, customized reduced chrome, and the disabling of browser add-ons.

Mileage Stats uses Pinned Sites to provide Windows 7 taskbar notifications that indicate whether the user has one or more overdue maintenance reminders. In addition, a dynamic jump list provides a direct link to each overdue maintenance reminder.

Mileage Stats taskbar integration

Hh404084.fc109b36-d981-4d89-8dc5-7ea19156f33b(en-us,PandP.10).png

Note

Jump list items will be available whether the site is opened in a browser or not. However, the notification icons are only displayed when the site is opened in the browser.

The two images below contrast Mileage Stats running in a normal browser window and a customized Pinned Sites browser window. The Pinned Sites image shows the cleaner, pared down browser window with potentially distracting browser features removed, allowing the user to focus on the application features. Applications run in the customized browser window when they are launched from a taskbar or the Start Menu Pinned Sites icon.

Mileage Stats without using Pinned Sites

Hh404084.65930ed3-8963-4da5-bf86-93b5fe8ccdc4(en-us,PandP.10).png

Mileage Stats using Pinned Sites

Hh404084.82995692-5de8-458e-869f-417ee7f49159(en-us,PandP.10).png

In addition to a cleaner browser window, Pinned Sites also allows the developer to customize the color of the browser back and forward buttons, and the browser displays the website favicon to the left of the back button. This favicon is also a link to the website home page.

Implementing Pinned Sites

Microsoft provides documentation for implementing Pinned Sites in their web applications on MSDN®. The title of this topic is, "Pinned Sites Developer Documentation," and is located at https://msdn.microsoft.com/en-us/library/gg491731(v=VS.85).aspx.

Pinned Sites in Mileage Stats

The following sections do not attempt to duplicate the MSDN documentation or cover every line of code pertaining to Pinned Sites. Instead, the Mileage Stats implementation will be explained, enabling you to understand the pieces, requirements, capabilities, and value of the Pinned Sites API.

The Pinned Sites implementation in Mileage Stats includes feature detection, site pinning, dynamic jump list updating, and display of notification icons. These features are encapsulated in the mstats.pinnedSite JavaScript object that is contained in the mstats.pinnedsite.js file. The pinnedSite object is initialized differently depending on whether or not the user is signed in. This initialization will be described below.

Feature Detection

Pinned Sites feature detection is provided by the Internet Explorer 9 msIsSiteMode function. Verifying that the page is opened as a pinned site before executing the Pinned Site API methods prevents unnecessary JavaScript errors.

The msIsSiteMode function returns true if the current page is launched as a pinned site and false if it is not. The following isPinned function wraps the msIsSiteMode call and returns false if the page is not launched as a pinned site, or the browser is not Internet Explorer 9.

// Contained in mstats.pinnedsite.js
isPinned: function () {
    try {
        return window.external.msIsSiteMode();
    }
    catch (e) {
        return false;
    }
}

Enabling Website Pinning

Unauthenticated users visiting the site are directed to the landing page, which is shown below. This page allows users to sign in, pin the site, and view the Mileage Stats video (not pictured). The Pinned Sites icon will glow when it is draggable, allowing the user to pin the site to the taskbar or Start Menu. The callout text displays for 5 seconds when the page loads. It will also show and hide the text as users move the mouse over or away from the Pinned Sites icon.

Note

Developers are not required to implement a draggable site icon as Mileage Stats does to enable site pinning. Providing a draggable icon gives the website more control over the pinning experience. Without a draggable icon, sites can still be pinned by dragging the tab or the favicon to the taskbar.

Landing page

Hh404084.a9a029fd-a1aa-4cc6-a2d9-0f2da448ae05(en-us,PandP.10).png

The Pinned Sites JavaScript object is initialized when the above page loads with the following JavaScript function.

// Contained in Index.cshtml
<script>
  $(function () {
    mstats.pinnedSite.intializePinnedSiteImage();
  });
</script>

If the browser is Internet Explorer 9 and the website is not currently pinned, the intializePinnedSiteImage method will attach appropriate event handlers for hiding and showing the callout text. It also adds the active CSS class to the Pinned Sites icon so that the icon appears to glow.

// Contained in mstats.pinnedsite.js
initializePinnedSiteImage: function () {

  // Do not enabled site pinning for non-Internet Explorer 9+ browsers
  // Do not show the callout if the site is already pinned
  if (!(!document.documentMode || this.isPinned())) {
    $('#pinnedSiteImage')
                .bind('mousedown mouseout', hideCallout)
                .bind('mouseover', showCallout)
                .addClass('active');
    $('#pinnedSiteCallout').show();
    setTimeout(hideCallout, 5000);
  }
},

The following HTML snippet shows the required msPinSite class applied to the Pinned Sites icon. This class is used by Internet Explorer 9 to enable the user to drag the Pinned Sites icon to the taskbar or Start Menu and pin the site.

// Contained in Index.cshtml
<img id="pinnedSiteImage" class="msPinSite" ... />

To call the user's attention to the draggable Pinned Sites icon, the active CSS class below adds an attractive outer glow to it.

// Contained in static.css
#pinnedSiteImage.active
{
    cursor: pointer;
    box-shadow: 0px 0px 15px #6Dffff, inset 0px 0px 10px #6Dffff;
    border-radius: 12px;
}

The user can pin a website by dragging the Pinned Sites icon, browser tab, or favicon to the taskbar or Start Menu. Internet Explorer 9 integrates with the Windows shell to accomplish the pinning.

Dynamic Jump List Updating and Notification Icons

Mileage Stats uses the jump list and notification icons to notify users of overdue maintenance reminders. When users click on the jump list entry, they will be taken to that reminder. The notification overlay icon displays 1, 2, 3, or 3+ to provide a taskbar indication of outstanding reminders.

Jump list and notification icon

Hh404084.5623c796-aa74-4374-9eb7-9edbc3e52f05(en-us,PandP.10).png

On the initial page load after the user authenticates, the client-side widgets and JavaScript objects are invoked by code in the mileagestats.js file. The pinnedSite object is initialized by passing it a delegate to the data manager's sendRequest method.

// Contained in mileagestats.js
mstats.pinnedSite.intializeData(mstats.dataManager.sendRequest);

The initializeData function saves the sendRequestFunc in the sendRequest property for future calls to the data manager by the requeryJumpList function.

// Contained in mstats.pinnedsite.js
intializeData: function (sendRequestFunc) {
  sendRequest = sendRequestFunc;
  this.requeryJumpList();
},

The requeryJumpList function, shown below, is called when the pinnedSite object is initialized, and also by the layout manager widget when a reminder is fulfilled. It's the layout manager's call that initializes the dynamic updating of the jump list and notification icon.

Note

Only the essential lines of code that demonstrate the loading of the jump list and updating of the notification icon are listed below.

All of the following msSite functions are provided by Internet Explorer 9. After using feature detection to determine if the site is pinned, the jump list and overlay icon are cleared, and a new jump list is created.

If the Ajax request is successful and the data.Reminders array has data, a URL will be constructed for each data item and added to the jump list. Next, the appropriate overlay icon is set. Finally, msSiteModeShowJumpList is called to update the jump list.

// Contained in mstats.pinnedsite.js
requeryJumpList: function () {
  var getRelativeUrl = mstats.getRelativeEndpointUrl;

  try {
    if (this.isPinned()) {

      sendRequest({
        url: '/reminder/overduelist/',
        contentType: 'application/json',
        cache: false,
        success: function (data) {

          try {
            var g_ext = window.external,
              ...

            g_ext.msSiteModeClearJumpList();
            g_ext.msSiteModeCreateJumpList("Reminders");
            g_ext.msSiteModeClearIconOverlay();

            if (data.Reminders) {
              for (i = 0; i < numReminders; i += 1) {
                reminder = data.Reminders[i];
                reminderUrl = getRelativeUrl('/reminder/details/' + 
                  reminder.Reminder.ReminderId.toString());
                g_ext.msSiteModeAddJumpListItem(reminder.FullTitle, reminderUrl, 
                  faviconUrl, "self");
              }

              if (numReminders > 0) {
                iconOverlayUrl = '/content/overlay-' + numReminders + '.ico';
                iconOverlayMessage = 'You have ' + numReminders.toString() + 
                  ' maintenance tasks that are ready to be accomplished.';
                if (numReminders > 3) {
                  iconOverlayUrl = '/content/overlay-3plus.ico';
                }
                g_ext.msSiteModeSetIconOverlay(getRelativeUrl(iconOverlayUrl), 
                  iconOverlayMessage);
              }
            }

            g_ext.msSiteModeShowJumpList();
          }
          ...

This code demonstrates that with a small investment, you can deliver dynamic desktop notifications in your websites.

Requirement for Jump List Items to Appear

The Windows 7 taskbar jump list items can be disabled by your users, preventing them from displaying even though the website has been pinned to the taskbar.

If your website implements the jump list feature, you should provide this information to your users and advise them that the Store and display recently opened items in the Start menu and the taskbar property setting needs to be checked for the jump list items to appear.

Taskbar and Start menu properties

Hh404084.a1e903f4-3c79-41b9-a6e5-781420502d7b(en-us,PandP.10).png

In addition to be being able to disable jump list items, users can customize the number of jump list items displayed on their computers. The default value is 10 and can be changed in the Customize Start Menu dialog box below. This dialog box is opened by clicking the Customize button in the Taskbar and Start Menu Properties dialog box shown above.

Customizing Start menu properties

Hh404084.a8b93771-b316-480e-b803-e3820c4971aa(en-us,PandP.10).png

Summary

Providing timely feedback that is uniformly displayed, context sensitive, and understandable to your users without breaking their workflow takes planning by designers and developers alike. Your users will appreciate this extra effort, which results in a polished UX. By encapsulating the display and management of user notifications in a single JavaScript object, your application will be easier to code, maintain, and test. You have also learned about integrating your website with the Windows 7 desktop to provide users with dynamic notifications and jump list items, as well as allowing them to browse your site using a customized browser window.

Further Reading

For a comprehensive look at input validation error messages, see Chapter 11, "Server-Side Implementation."

For detailed information on the inner workings of the Mileage Stats pub/sub implementation, see Chapter 8, "Communication."

For more information about the isFunction method, see jQuery.isFunction():
http://api.jquery.com/jQuery.isFunction/

For more information about Pinned Sites, see the Pinned Sites developer documentation:
https://msdn.microsoft.com/en-us/library/gg491731(v=VS.85).aspx

Next | Previous | Home | Community