AngularJS Toaster Notifications with Domino

This blog is going to demonstrate how to read some Domino Data via the REST API and then trigger a toaster notification in the browser.  It will also demonstrate how we can integrate a small AngularJS module into a standard Domino design element (a page in this instance).

The scenario is we want to display a visual notification to the user when they initially load a page – this notification is specific to them and it can be sticky or only appear for a certain amount of time.

For sticky notifications if the user clears the notification we want to update the Notes document so the notification does not reappear if the user refreshes the page, if its a transient notification we want to update the Notes document once it disappears.

There is a simple demo Notes application which contains the working code and you can download it from here.

This demo uses a custom directive created by someone else to present the notifications – https://github.com/jirikavi/AngularJS-Toaster  – I have modified this version to allow me to pass the UNID of the associated Notes document as well as handle the closure of the notification to update the Notes document.

If your a beginner to AngularJS there might seem to be a lot going on – I will try and break it down.

Demo

To give you an idea what I am talking about I have embedded an example which uses a JSON data file rather than Domino to provide the data.  When the notifications are cleared instead of updating a document a list on the page updates.

This AngularJS demo has the following features:

In the Javascript file:

  • Loads Data via HTTP
  • Demonstrates how to use a 3rd party directive within your code
  • Has a watcher method to respond when the data is loaded.
  • Has an event handler for handling when the directive broadcasts something.

The HTML page demonstrates the use of Angular directives which are mixed into the markup – specifically:

  • ng-click – calls a function.
  • ng-cloak – hides the page until the AngularJS App has finished loading (prevents the flash of bound content)
  • ng-disabled – disables a button once a criteria has been met.
  • ng-hide – hides a page element
  • Filter – specifically for dumping the contents of an object out as JSON.
  • Binding – binds objects in the associated model to the display.

To keep things simple the demo database has the toaster.js script included as a script library resource.  The demo.html page is actually a page element within the application, I have done this to make it easy to pick up the current logged in user (using a computed value), the custom logic is then included in a script block at the bottom of the page.

In future blogs I will demonstrate how this is not required and you can use pure file based HTML files, passing the credentials of the logged in person to AngularJS.

I still recommend you work in Webstorm with file resources within the HTML directory and if you need to use a computed value to pick up the current user then transfer to the Domino App once your happy it all works.

Data Setup

In my embedded example I use a static JSON file but we are going to use Domino Data Services in conjunction with the demo database.

First up you need to ensure you have configured the Domino Application and Server to support the Domino Data Services.

Configure the Server

Assuming you have an internet site document:

SNAGHTML4320f98

Configure the Database (already done on the included demo app)

image

REST API Calls

We will be doing a GET REST API call on a view which is categorised by the name of the individual listed in the notification document.

Example: /notification.nsf/api/data/collections/name/(LUNotificationsAwaiting)?category= – computed value is @username – you can see this in the demo database

We will also issuing a PATCH REST API call to update the notification document once it has been seen.

Example: */notification.nsf/api/data/documents/unid/’+ toasterObj.unid *- toasterObj has the Notes Document UNID set on it as the toaster object is created.

Important

Make sure your internet site document has PATCH as an allowed method:

image

Example Notification Documents

The example database allows you to create Notification documents – go ahead and create some now:

Hopefully the fields make sense – sticky just means the user has to click to clear the notification else it will disappear after 3 seconds.

Make sure you create some notifications for the id of the user who will open the page element.

Once done launch the demo page – e.g. http://YOURSERVER/notification.nsf/demo.html

The ACL has anonymous set to no access so you should get prompted to authenticate first.

The HTML Page

Finally we get to talk about the code – starting with the demo.html page (which is actually a page in the database as discussed).

You can spot the Angular stuff because they all start with ng-tag.

First up we have the

<html ng-app="barcode">

This is used to bootstrap the Angular Application and is associated in the script block

var app = angular.module("demo", ['toaster'])

Note the demo = demo

The resources we are using in the Head of the HTML page is Bootstrap 3, AngularJS, AngularJS Animate, a customised version of toaster.js and a CSS file which is required by the custom toaster directive which is provided by – https://github.com/jirikavi/AngularJS-Toaster

These resources are being loaded from a CDN (rather than me including them).

Next up is the controller reference:

<body ng-controller="demoController" ng-cloak>

There has to be at least 1 controller per application, there can be several and they can be nested – the controller is responsible for controlling a block of the view and it has to be defined on a DOM container (DIV, FORM etc).

Its referenced in the code here:

app.controller("demoController", function($scope, $http, toaster) {

ng-cloak is used to hide any angular bits – specifically bindings until Angular has fully initialised.

Next we have the button

<button class="btn btn-success">Launch Demo</button>

The ng-click is calling a function within my controller – called loadNotifications and looks like this:

$scope.loadNotifications = function() { $http( {url: 'sample.json' //url: '/notification.nsf/api/data/collections/name/(LUNotificationsAwaiting)?category=<Computed Value>' }).success(function(data, status, headers, config) { $scope.notifications = data; }) }

Note the use of $scope, this is provided to you by Angular and it defines what is in scope for the controller – this way your javascript code does not leak and pollute the Global scope.

ng-disabled – This directive is used to disable the button (in this case) when the length of an array within the controller is greater than 0 – because of AngularJS binding – as soon as this array length changes the button will automatically be disabled.

Next we have:

<div class="panel panel-default" ng-hide="demoClickedToasts.length==0">

This is similar to disabled on the button – instead we are hiding a section of the page until we have some data – again it will automatically display because of the binding.

Next up:

<ul class="list-group"> <li class="list-group-item" ng-repeat="toast in demoClickedToasts">

This is an example of ng-repeat – which as the name suggests is a repeat control – Steve has some more detail on ng-repeat but hopefully from this you can see toast is a reference to each of the toast objects in the demoClickedToasts array which is held in the scope on the controller, as Angular repeats for each object it extracts the title property and displays it (due to the binding).

Finally:

We have an example of one of the inbuilt filters that AngularJS provides – this one will convert a object into a JSON representation and is useful for debugging purposes

{{demoClickedToasts || json}}

The final element is the markup for the custom toaster directive:

<toaster-container toaster-options="{'time-out': 3000,'spinner':false}"></toaster-container>

Custom directives can be complex so now is not the time to explain it – other than you can see you can create your own custom HTML elements.

Our Script block would normally be in a file but to keep things simple and as we need the computed value for the username we put it at the bottom of the page.

The Code

We first setup an empty object to hold the result of getting any Notifications.  This is placed on the $scope so other functions within our controller can get to it.

//Initial Holding Object for our Notifications - set as empty $scope.notifications = {};

Loading Data.

For this example we are using $http

//Initial Holding Object for our Notifications - set as empty $scope.notifications = {}; //Load Notifications for this current user from the View using the Domino REST API //For the demo we will use a local sample.json file which mimics the Domino JSON Data $scope.loadNotifications = function() { $http({ url: '/notification.nsf/api/data/collections/name/(LUNotificationsAwaiting)?category=<Computed Value>' }) .success(function(data, status, headers, config) { $scope.notifications = data; }) }

We then use the $http method to call our REST API, upon a success callback the notifications object is set to the results.
NB We are not handling errors here but you should.

The Watcher

$scope.$watch('notifications', function(newVal, oldVal) { if (oldVal == newVal) return; //We have notifications - loop through them and show them $scope.displayNotifications(); });

One of the inbuilt methods of AngularJS allows us to watch the value of a object and if it changes perform an action, that’s what this code is doing – its making sure that the value for notifications has actually changed and if it has it calls the displayNotifications method.  We could have put displayNotifications in the success handler of the loadNotifications method but this would mean that the loadNotifications code is tightly coupled to the displayNotifications so we couldn’t just call loadNotifications without displaying them.

Event Capture

$scope.$on('toaster-Removed', function(event, toasterObj) { $scope.handleNotificationClick(toasterObj.toaster) });

Because the custom Toaster Directive lives in its own scope it needs to pass to the custom code when the notification has been clicked – this code is listening for that broadcast.

Display the Notifications

$scope.displayNotifications = function() { angular.forEach($scope.notifications, function(value, key) {
if (value.sticky == "No") { sticky = 3000} else {sticky = 0}
toaster.pop(value.type, value.title, value.message, sticky, 'trustedHtml', "", value['@unid']); })}

This function is called by the watcher and it loops over the notifications object – its looking to see if the Notes document field for Sticky has been set to No – if it has then it sets the sticky value to 3000 ms else 0.

It then calls the toaster method to pop the notification passing the different values extracted from the REST API call – NB the way to extract a JSON property that has a @ in it is to use [].

Finally update the documents.

$scope.handleNotificationClick = function(toasterObj) { $http({ method: "PATCH", url: '/notification.nsf/api/data/documents/unid/'+ toasterObj.unid, data:{'Status':'Seen'} }) }

This $http calls the Domino REST API but this time as a PATCH request and it sets the data as Status=Seen – which on the Notes document will update the corresponding field with that value.

Conclusion

Hopefully this has given you some idea how a simple AngularJS custom directive can be embedded into your Domino code.

Edit>> As previously both Steve Chapman and Marky Roden have new AngularJS blogs out today.