Notification API with Page Visibility API

When writing with Javascipt for the web, there are many APIs available for us to use. Some are very mature such as DocumentFragment while some are more experimental (yet still usable) like WebGL. In this article we combine two of these APIs to make a feature that many websites implement, sending messages to the user using the native browser notification (not to be confused with push notifications).

 Notification API

Screen Shot 2016-04-02 at 4.15.29 PM.png

The image above is a simple example of a native browser notification. In the example, we use the title, body, and icon properties.

Properties:

 Create Notification

const title = 'Hi i\'m Chris!';
const body = 'Notification API with Page Visibility API';
const icon = '/icon.ico';
const options = {body, icon};

const notification = new Notification(title, options);

But wait, can the user just start spamming notifications? Short answer: no. You would need to first request the user’s permission using the call requestPermission() and then once approved, calls for new Notification() would generate a notification instance (and alert).

Screen Shot 2016-04-02 at 4.11.47 PM.png

 Notification Permission

function callback(result) {
  ...
}

const promise = Notification.requestPermission(callback);

promise
  .then((result) => {
    ...
  }).catch((result) => {
    ...
  });

The argument result used in the callback functions above is based on what how the user interacts with the prompt popup.

Both the callback and the promise will be resolved once the user interacts with the popup prompt.

 Actions, Events, and Behaviour

So cool, you can now send a notification to your users. But now your UX will suffer because of you did not handle user interactions with the notification like the following:

 Clicking on the notification

Fortunately, the notification API provides us with a hook to manage this scenario. Simply pass a function to the onclick property of the notification object and it will fire once the user clicks on the notification.

const notification = new Notification(title, options);
notification.onclick = (event) => {
  event.preventDefault();
  window.focus();
  event.currentTarget.close();
}

Use the event.preventDefault() call to stop the default behaviour of opening a new tab in the background when launching links rather than being focused on the new tab. However in our case, we are just closing the notification onclick.

This function assigned to the onclick allows us to have reference back to the original window (and if you use scope changing methods like bind, even have reference the current scope). Calling window.focus() brings the user back into the site that launched the notification. Finally we close the notification dialog when the user clicks on it by using the Notification API given method close().

 Timing out

Some browsers automatically removes a notification after a certain period of time, but some take longer than that. To keep your UX consistent you should really take control of the browsers that take longer by closing the notifications if the users have not interacted with it. A simple timeout call when you create a new Notification will suffice.

const notification = new Notification(title, options);
const timeoutTime = 5000; // 5 seconds
setTimeout(() => {
  notification.close();
}, timeoutTime);

Similarly to the onclick method, we just close the dialog using the Notification API given close method.

 Being headless

So in the rare occurrence that a notification is launched but either the user closes the page or the page closes, the notification dialog will lose reference to the page. Meaning stuff like window.focus() or other bind-ed methods won’t work anymore and could result in an error.

To handle this, make sure to have an onDestroy or similar method that will clean up any opened notifications and either change the onclick behaviour or close it.

Keep all opened notifications in a data structure so you can reference them all later when cleaning up.

 Spamming users

We must make sure not to ask the user again for access to launch notifications when they have already denied it, or does not support it. Nicely, the requestPermission() call returns the string denied in the promise payload when the user has already denied the access. However if you have some UI to trigger the requestPermission() call, this needs to be handled separately along with user-agents who don’t support notifications.

const supportsNotification = Boolean('Notification' in window);
const permissionAlreadyDenied = supportsNotification && Boolean(Notification.permission === 'denied');

const doNotShowUI = !!permissionAlreadyDenied;

Finally, even if we asked for the users’ permission that doesn’t mean we should keep alerting them using notifications. A good rule of thumb is to not send any notifications to the user when they are actively in the page. The Page Visibility API provides us data to whether the user is active in the page or not.


 Page Visibility

The Page Visibility API lets you know when a website is visible or in focus. When the user minimizes the webpage or moves to another tab, the API sends a visibilitychange event to let you know the visibility state of the website.

Historically, this detection has been done by registering onblur and onfocus handlers on the window. However onblur or onfocus does not handle when the page is not visible to the user rather than just not being focused upon.

const handleVisibilityChange = () => {
  if (document[hidden]) {
    // do actions for hidden page
  } else {
    // do actions for unhidden page
  }
}
const visibilityChange = 'visibilitychange'; // might be prefixed

document.addEventListener(visibilityChange, handleVisibilityChange, false);

Visibility states of an iframe are the same as the parent document. Hiding the iframe with CSS properties does not trigger visibility events nor change the state of the content document.

 Page Visibility Properties

Document state is handled by the property document.hidden which returns either true or false if the page is hidden (not visible) or not.

Different document.visibilityState property states (or its prefixed version):


 Notification + Page Visibility and beyond

So together the Notification API and the Page Visibility API brings a dynamic behaviour for alerting users of a completed event such as searches, processing completion, and etc.

One thing to keep note for both APIs is to make sure to have the prefixes built into your project since this is not currently supported (without prefixes) in some browsers.

To take it further we can:


tldr; Use the Page Visibility API to conditionally show the native notification alerts when the page is not visible while doing everything that is possible to make native notifications unobtrusive to the user (and make sure to A/B test this feature in).

 
20
Kudos
 
20
Kudos

Now read this

Spirally Traversing a Two-Dimensional Array in JavaScript

This is a way to traverse a two-dimensional array (or a matrix) from the top left, spirally in a clockwise manner in JavaScript. This solution is inspired by functional programming and it uses tail recursion. Steps: When you start with... Continue →