Using Alarms to notify users

This article is in need of a technical review.

The Alarm API is another tool at your disposal for providing a user with a system notification, using a more simplistic workflow for automatically setting notification deadlines and checking when they are due, rather than having to do all this manually via some kind of regular timer and complex checking function like we did in the previous article. This article shows how it's done.

A web UI showing a title of To do list, an entered item of Take dog to vets, and several form elements to enter to do list dataThe reference example

The example referred to throughout this article is To-do List Alarms, a to-do list example that stores to-do list information (task and time and date task is due) in an IndexedDB instance. When a to-do list task is stored, the Alarm API is used to store an Alarm reference in the system, which is handled by the same application. When the alarm is due, a system notification is shown to notify the user of this, which works whether the application is open at the time or not.

Browser support is as follows:

  • The IndexedDB part of the application should work in Firefox desktop/android/OS, IE 10, Chrome and Opera 16+
  • The alarm and notification part of the application is pretty experimental right now, and currently only works on apps installed on Firefox OS 1.2+ It is possible to get notifications working on other systems (as detailed in Notifying users...), but in this case the notifications rely on the alarms.

The To-do List Alarms code is available on Github, so grab it and play around with it. If you want to test it out on a real device, I'd recommend using the Firefox OS App Manager.

This article won't focus much on IndexedDB: if you are interested in how that part of the app works and haven't got much experience with IndexedDB, check out our IndexedDB documentation, starting with Using IndexedDB.

With the example introduced, lets start working through the parts of the example relevant to setting our alarms.

Adding new data, and setting our alarms

One of the most important functions in the JavaScript for this file is addData() (see todo.js in the app): this grabs the data entered into the form, stores it in the IndexedDB, and then sets an alarm in the Firefox OS system. Let's go thorugh it bit by bit.

function addData(e) {
    // prevent default - we don't want the form to submit in the conventional way
    e.preventDefault();
    
    // Stop the form submitting if any values are left empty. This is just for browsers that don't support the HTML5 form
    // required attributes
    if(title.value == '' || hours.value == null || minutes.value == null || day.value == '' || month.value == '' || year.value == null) {
      note.innerHTML += '<li>Data not submitted — form incomplete.</li>';
      return;

We begin by preventing standard form submission, and doing a check to make sure the form has been filled in: if there are empty fields, we don't submit and give the user a message to say that the form isn't complete (this is added into the messages display at the bottom left of the UI; run the app and check it out.)

Note: I have also included required attributes on the form elements (look at the index.html file to see them) to deal with the client-side validation automatically, HTML5 style! The above if() block is only really for browsers that don't understand HTML5 forms.

    } else {
      // test whether the Alarm API is supported - if so, we'll set a system alarm
      if(navigator.mozAlarms) {
        //build a date object out of the user-provided time and date information from the form submission
        var myAlarmDate  = new Date(month.value + " " + day.value + ", " + year.value + " " + hours.value + ":" + minutes.value + ":00");

        // The data object can contain any arbitrary data you want to pass to the alarm. Here I'm passing the name of the task
        var data = {
          task: title.value
        }

When the form is successfully submitted, we now turn our attention to our alarms! We first check to see if the browser supports the Alarm API using if(navigator.mozAlarms). If the mozAlarms object exists, we then proceed to setting the alarm. We first need a valid Date() object to state when the alarm should be set for; the long assignment value of myAlarmDate above shows how we construct this out of the values inserted into the form elements. Last for this section of code, we store the task title in an arbitrary data object for use in setting the alarm below.

        // The "ignoreTimezone" string makes the alarm ignore timezones and always go off at the same time wherever you are
        var request = navigator.mozAlarms.add(myAlarmDate, "ignoreTimezone", data);

        request.onsuccess = function () {
          console.log("Alarm sucessfully scheduled");

          var alarmRequest = navigator.mozAlarms.getAll();
          alarmRequest.onsuccess = function() {
            newAlarmId = this.result[(this.result.length)-1].id;
          }
        };

We now request the navigator.mozAlarms.add() method to set a new alarm in the Firefox OS system. This takes three arguments:

  • A valid Date() object like the one we constructed earlier, to state when the alarm should be set for.
  • A keyword to state whether this alarm should ignore timezones (ignoreTimezone), and always go off relative to the time the current device is set at, or honor timezones (honorTimezone), and go off at different times depending on what timezone the current device is set to (the system will detect the difference between the alarm's time zone, and the timezone of the current device, and adjust the time accordingly.)
  • An arbitrary data object (like the one we set above), which can contain whatever data you want to associate with the alarm being set.

Next, inside the onsuccess handler of the request we execute some code to grab the id of the alarm that has just been set. To do this, we first use the getAll() method of mozAlarms to request an array containing objects representing all the alarms (alarmRequest). We can grab the id of each one using the alarm's id property, but how do we grab just the latest one? The answer is that the latest one is the last one in the array, so we grab it using this.result[(this.result.length)-1], then get its id and store it in a variable using newAlarmId = this.result[(this.result.length)-1].id.

Note: The newAlarmId variable was set at the top of the code, so it is global.

        request.onerror = function () {
          console.log("An error occurred: " + this.error.name);
        };
      } else {
        note.innerHTML += '<li>Alarm not created - your browser does not support the Alarm API.</li>';
      };

The next section handles the failure cases: if the request to set the alarm failed, we log its error message. If the Alarm API is not supported at all, we add a message to the application's message box to tell the user that alarms are not supported on their device.      

      // grab the values entered into the form fields and store them in an object ready for being inserted into the IDB
      var newItem = [
        { taskTitle: title.value, hours: hours.value, minutes: minutes.value, day: day.value, month: month.value, year: year.value, notified: "no", alarmId: newAlarmId }
      ];

      // open a read/write db transaction, ready for adding the data
      var transaction = db.transaction(["toDoListAlarms"], "readwrite");
    
      // report on the success of opening the transaction
      transaction.oncomplete = function(event) {
        note.innerHTML += '<li>Transaction opened for task addition.</li>';
      };

      transaction.onerror = function(event) {
        note.innerHTML += '<li>Transaction not opened due to error. Duplicate items not allowed.</li>';
      };

      // call an object store that's already been added to the database
      var objectStore = transaction.objectStore("toDoListAlarms");
      // add our newItem object to the object store
      var request = objectStore.add(newItem[0]);        
        request.onsuccess = function(event) {
          
          // report the success of our new item going into the database
          note.innerHTML += '<li>New item added to database.</li>';

Moving on! This next code section handles adding the new to-do list item to the IndexedDB. I'll not explain this here, so for more help with IndexedDB, read Using IndexedDB.

          
          // clear the form, ready for adding the next entry
          title.value = '';
          hours.value = null;
          minutes.value = null;
          day.value = 01;
          month.value = 'January';
          year.value = 2020;
          
        };
         
      };

Now we clear the form, ready for the next to-do list item to be entered.

    
      // update the display of data to show the newly added item, by running displayData() again.
      displayData();
    };

Last of all, we run the displayData() function, which updates the display of the to-do list items at the top of the app UI.

Responding to a set alarm

We have just set an alarm in the Firefox OS system, but that's all it is so far, just a system message. How do we respond to the alarm when it goes off? This can be handled from any application, using the currently Firefox OS-only navigator.mozSetMessageHandler() method, and the code is really quite simple.

 // This will handle an alarm set by this application
  if(navigator.mozSetMessageHandler) {
    navigator.mozSetMessageHandler("alarm", function (alarm) {
      // only launch a notification if the Alarm is of the right type for this app
      if(alarm.data.task) {
        // Create a notification when the alarm is due
        new Notification("Your task " + alarm.data.task + " is now due!");
        updateNotified(alarm.data.task);
      }
    });
  }

Our alarm notification showing in Firefox OS.

In this code we first use if(navigator.mozSetMessageHandler) to check whether it is supported before running the rest of the code. If it is, then we use navigator.mozSetMessageHandler() to run a function when the alarm happens. This takes two arguments:

  1. The type of message to respond to, in this case, alarm (basically, a system message is sent when an alarm goes off.)
  2. A function to run when the alarm message is sent. Here we have an anonymous function that accepts the alarm as its argument.

Inside the function we could handle notifying the user that the alarm has happened in any way we like, but we will do it using a system notification, as this will show whether we have got the app open at the time or not. We first run an if(alarm.data.task) check, to check whether the data.task data item is attached to this alarm (remember the arbitrary data we attached to the alarms in the addData() function we looked at above?) We do this because we don't want to fire a notification for every alarm in the system, just alarms that have been set by our application. Checking the alarm data is a convenient way to do this.

If this check passes, then we do two things:

  1. Create a system notification with new Notification("Your task " + alarm.data.task + " is now due!"); — this will appear on the system as shown in the screenshot to the left.
  2. Run the updateNotified() function, which changes the notified property of the current task's value from No to Yes; the value of this property is checked for each task when displayData() is called, and tasks that have already had a notification fired for them are given a different styling in the app UI.

Note: For more details on using the Notification API, read Notifying users via the Notification and Vibration APIs.

Removing an alarm

The last part of the code relevant to our current article is contained within the deleteItem() function.

function deleteItem(event) {
    // retrieve the name and the alarm ID of the task we want to delete
    var dataTask = event.target.getAttribute('data-task');
    var dataAlarm = event.target.getAttribute('data-alarmId');

    //delete the alarm associated with this task
    if(navigator.mozAlarms) {
      navigator.mozAlarms.remove(dataAlarm);
    }
    
    // delete the parent of the button, which is the list item, so it no longer is displayed
    event.target.parentNode.parentNode.removeChild(event.target.parentNode);
    
    // open a database transaction and delete the task, finding it by the name we retrieved above
    var request = db.transaction(["toDoListAlarms"], "readwrite").objectStore("toDoListAlarms").delete(dataTask);
    
    // report that the data item has been deleted
    request.onsuccess = function(event) {
      note.innerHTML += '<li>Task \"' + dataTask + '\" deleted.</li>';
    };
  }

Each item listed in the UI using the displayData() function has a delete <button>; each one is given a data-alarmid attribute containing the id of the alarm associated with this item, and a data-task attribute containing the title of the task. When a delete button is pressed, the deleteItem() function is run, which first grabs these id and title values out of those attributes and stores them in variables:

var dataTask = event.target.getAttribute('data-task');
var dataAlarm = event.target.getAttribute('data-alarmId');

Next, we check to see if the Alarm API is supported, and if so, use the mozAlarms.remove(); method to remove that particular alarm:

if(navigator.mozAlarms) {
      navigator.mozAlarms.remove(dataAlarm);
    }

Then we remove the list item containing the button that was clicked on, using this rather convoluted line:

event.target.parentNode.parentNode.removeChild(event.target.parentNode);

Last of all, we delete the item with that task title out of the IndexedDB, then write a message out to tell the user that task was deleted:

// open a database transaction and delete the task, finding it by the name we retrieved above
    var request = db.transaction(["toDoListAlarms"], "readwrite").objectStore("toDoListAlarms").delete(dataTask);
    
    // report that the data item has been deleted
    request.onsuccess = function(event) {
      note.innerHTML += '<li>Task \"' + dataTask + '\" deleted.</li>';

Permissions for the Alarm and Notification APIs

Please note that while the Alarm and Notification APIs is not privileged or certified, you should still include permissions and messages entries in your manifest.webapp file when including them in an installable open Web app.

"permissions": {
    "desktop-notification" : {
      "description": "Required to schedule notifications"
    },
    "alarms": {
      "description": "Required to schedule alarms"
    }
  },
  "messages": [
    { "alarm": "/index.html" },
    { "notification": "/index.html" }
  ]

Attachments

File Size Date Attached by
To do app screenshot
A web UI showing a title of To do list, an entered item of Take dog to vets, and several form elements to enter to do list data
54617 bytes 2013-12-09 07:38:40 chrisdavidmills
FxOS notification
Our alarm notification showing in Firefox OS.
156225 bytes 2013-12-09 10:38:56 chrisdavidmills

Document Tags and Contributors

Contributors to this page: chrisdavidmills
Last updated by: chrisdavidmills,