AppSuite:Writing a notification area plugin (7.6.x)

From Open-Xchange

API status: In Development

Writing a plugin for the notification area (7.6.x)

Abstract: This article is a step by step tutorial to build your own notification plugin. These plugins can be used for various purposes, for example reminding the user of something or showing him new invitations.

Preparations

To start a new plugin for the notification area you have to add a new folder at apps.plugins/notifications/ . For this tutorial we will create plugins/notifications/tutorial/ .

Now add a new file to your folder and name it register.js . Then add the basic markup such as copyright, define for require.js, use strict and so on. In our tutorial the result looks like this.

/**
 * your copyright here
 * @author Mister Test <mister.test@test.test>
 */
 
define('plugins/notifications/tutorial/register',
    ['io.ox/core/extensions'], function (ext) {

    'use strict';
    
    //just to give something back
    return true;
});

Note: We need to use extensions so we need to require the needed resources with

 ['io.ox/core/extensions'], function (ext)

as seen above.

Manifests

Your file is not loaded yet. To do this we need to create a manifest file. Create a new file in your folder with the name manifest.json with the following code in it:


{
	namespace: "io.ox/core/notifications"
}

For developing add the following code to src/manifests.js

{
    namespace: ['io.ox/core/notifications'],
    path: 'plugins/notifications/tutorial/register'
}

For further information about manifests look here.

Coding the base

Registering your plugin

Now you need to register your plugin by extending the right extension point, which is io.ox/core/notifications/register . Give your plugin a unique id, indexnumber and a register function. Inside this function we register our notification plugin at the controller and also give it an id and our view, we create later on. Do so by adding:

 
//register our notification plugin
ext.point('io.ox/core/notifications/register').extend({
    id: 'tutorial',//unique id
    index: 500, //unused index
    register: function (controller) {
        //give our plugin a name and send it to the controller together with our view
        var notifications = controller.get('io.ox/tutorial', NotificationsView);
    }
});

io.ox/tutorial is the id the controller should use to refer to our plugin and NotificationsView is the view we will create now to display it.

Creating the View

Since we use will use backbone to create our plugin it obviously needs a view. Call the variable like the one you gave to the controller to make it work. In our example the code to create the view looks like this.

 
//the view of our plugin
var NotificationsView = Backbone.View.extend({

    className: 'notifications',
    id: 'io-ox-notifications-tutorial',
        
    //events from our items
    events: {
    },

    //draws the plugin
    render: function () {
        return this;
    }
});

Note: Events and render function are empty at the moment, but we will fix that soon.

Adding the headline

Now we want to draw something. We could just put it in the render method, but since we have the extension point architecture we will make use of it, to keep our actual render method cleaned up.

We start by creating an extension point we will use to draw our headline and create a container for our notifications to put in later. Add this code to your views render method to create the point and invoke the draw method of its extensions.

 
//build baton to wrap things up
var baton = ext.Baton({ view: this });
//draw header and container by creating an extension point and invoke drawing on its extensions
ext.point('io.ox/core/notifications/tutorial/header').invoke('draw', this.$el.empty(), baton);

Note: Here we use our special baton objects to pass the data to the extension points. By using this.$el.empty() as a dom node to draw we ensure that we clean up properly before drawing.

Now extend the point with a simple draw method for our header and container. this refers to our views dom node we draw in in the rendering method.

 
//the header and container for the notification plugin
ext.point('io.ox/core/notifications/tutorial/header').extend({
    draw: function (baton) {
        this.append(
            $('<legend class="section-title">').text('Hello World'),//header
            $('<div class="notifications">')//the container for our notifications
        );
    }
});

Congratulations the first steps are done. Time for some testing to see if we did it right.

First testing

Now we want to see how it looks in the program. To do this add this line of code to your register method:

notifications.collection.reset(new Backbone.Model());

This creates an empty notification model and adds it to our plugins collection. We get to know how this collection works later on, for now we just need something in it for the controller to think that there is a notification to display.

When finished start your appsuite and login, add &customManifests=true to the url to load your plugin and reload the page.

Note: The notification area is loaded with a delay, so you have to wait a bit.

After it's loaded you should see something like this:


Header.png

Well done now we need to add some real notifications.

Adding Notifications

Triggering the events

Normally a notification area listens for events of the app it is related to. For example the mail notifications, listen for events on from the mail api.

For this tutorial we will just create a small dummy to create some Notifications for us and then trigger the proper event.

Our dummy looks like this:

/* simple helper to trigger some events
   normally this is done by mailapi, taskapi, etc. */
var myEventTriggerer = {
        lookForItems: function () {
            //build some items and put them in an array
            var items = [{title: 'I am a notification', description: 'Hello world!'},
                         {title: 'I am a notification too', description: 'Hooray!'}];
            //trigger the event to add them
            $(myEventTriggerer).trigger('set-tutorial-notification', [items]);
        }
    };

This dummy creates an array with two objects containing our notifications data. Then it triggers an event on itself and passes the array as an argument.

Helper functions

Notifications are stored as backbone models in a collection of our view. Or models have the attributes title and description. A cid is added automatically that we use to identify them later on. You can also give ids as normal attributes and use them if you want more control over it. This collection is available in the register method under notifications.collection. The controller looks for changes in this collection and triggers a redraw.

To do this we create a simple functions for resetting notification models in our collection. Remove our testing line notifications.collection.reset(new Backbone.Model()); from the register method and add our new function:

 
/* fill our collection of notification models with new ones
   items here is an array of Objects containing our attributes */
function reset(e, items) {
    var models = [];
    items = [].concat(items);//make sure we have an array
    _(items).each(function (item) {
        models.push(new Backbone.Model(item));
        });
    notifications.collection.reset(models);//fill the collection
    }

This function simply loops over the array of items they are given, creates models from them and fills the collection with it. Reset means that the old models are gone now and only the new ones are in our collection. Functions to add and remove models are done the same way but are not always needed, as in this example.

Note: notifications.collection.add() does not trigger the add event. You need to do this manually, this seems to be a backbone issue.

Listen for events

So now we need just listen to the event to launch our reset function and call our little dummy to fill our initial collection. We do this by adding this code to the register method.

 
//now add the event listeners
$(myEventTriggerer).on('set-tutorial-notification', reset);

//just to make sure it gets filled with values on load we trigger our search method
myEventTriggerer.lookForItems();

Drawing the Notifications

Now that we have proper models, we need to draw them. To do this we do the same as with the header, create an extension point and invoke drawing. In our views render method we need to add:

 
//loop over collection and draw
this.collection.each(function (model) {
    baton = ext.Baton({ model: model, view: this });
    ext.point('io.ox/core/notifications/tutorial/item')
        .invoke('draw', this.$('.notifications'), baton);//draw the item
}, this);

This time we draw in the container div we created by our header drawing method. Next we extend our extension point to draw the actual notification items.

In our example it looks like this:

 
//draw a single notification item
ext.point('io.ox/core/notifications/tutorial/item').extend({
    draw: function (baton) {
        this.append(
        $('<div class="tutorial item">')
        .attr('data-cid', baton.model.cid)//needed for identification
            .append(
                $('<div class="mytext">').text(baton.model.get('title')),//some text to fill it
            )
        );
    }
});

As you can see we add div with the classes tutorial and item and give it the attribute data-cid which we fill with our models cid or your custom id from the models attributes for later identification. After that we add a node and fill it with the title text of our model.

Open up your Appsuite and check if everything works. Be sure to load the custom manifests if needed. It should look like this:

Notifications.png

Adding functionality

Adding a Sidepopup

A notification without functions is a bit boring, so lets add some action to them by opening a sidepopup and draw the description in it.

Change your views events so it looks like this.

 
//events from our items
events: {
    'click .item': 'openPopup',
},

This calls the openPopup function of our view if you click on a div with the class item, which, in this case, are our notifications. We will create the openPopup function now, add this function to your view:

 
//the action for our sidepopup
openPopup: function (e) {
            
    var overlay = $('#io-ox-notifications-overlay'),//the overlay we draw our sidepopup in
        cid = $(e.currentTarget).attr('data-cid'),//getting the right model
        model = this.collection.getByCid(cid);
            
    require(['io.ox/core/tk/dialogs'], function (dialogs) {//require dialogs
        //create the popup
        new dialogs.SidePopup({ arrow: false, side: 'right' })
            .setTarget(overlay)
            .show(e, function (popup) {
                //fill it with our data
                popup.append($('<div>').text(model.get('title')),
                             $('<br>'),
                             $('<div>').text(model.get('description')));
            });
    });
}

First we define some variables. overlay is the div we append our popup to, cid is the cid we added as an attribute to our notification div earlier and model is the model we get from our collection by this cid. After this we require our dialogs plugin and create a new sidepopup. The target we draw it on is the overlay we grabbed earlier. In the show method we draw the contents of our popup. In this case its the models title variable and description variable.

Time to check if it works. In the appsuite your notifications should now open a sidepopup if you click on them.

Add a remove option

We don't want our notifications to stay there forever, so we need a way to remove them. Let's add a button to do this.

Add the button in your item draw function:

ext.point('io.ox/core/notifications/tutorial/item').extend({
    draw: function (baton) {
        this.append(
        $('<div class="tutorial item">')
        .attr('data-cid', baton.model.cid)//needed for identification
            .append(
                $('<div class="mytext">').text(baton.model.get('title')),//some text to fill it
                $('<button class="mybutton btn btn-primary" data-action="close">').text('close')//close button
            )
        );
    }
});

Now add an event to your view to be triggered on clicking it.

events: {
    'click .item': 'openPopup',
    'click [data-action="close"]': 'closeNotification'
}

This will call the closeNotification if the user clicks on a dom node where the attribute data-action has the value close, like our button.

Finally add the close funktion to your view:

 
//the action for the close button
closeNotification: function (e) {
    e.stopPropagation();//to prevent sidepopup from opening
            
    var cid = $(e.currentTarget).closest('.item').attr('data-cid'),//getting the right model
        model = this.collection.getByCid(cid);
            
    this.collection.remove(model);//remove it from the collection
}

Again get the cid to identify the right model, then remove it from the collection. e.stopPropagation() is important here because otherwise the event would bubble up and trigger the click event for opening our sidepopup too.

Congratulations your notifications should be removed if you click the button.

The final version should look like this.

Finished.png

Download

You can download the examplecode here.

File:Notification tutorial.zip.

Stuck somewhere?

You got stuck with a problem while developing? OXpedia might help you out with the article about debugging the UI.