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

From Open-Xchange
No edit summary
No edit summary
 
(29 intermediate revisions by 6 users not shown)
Line 1: Line 1:
<div class="title">Writing a plugin for the notification area</div>
{{Stability-experimental}}
 
<div class="title">Writing a plugin for the notification area (7.6.x)</div>
 
'''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.


__TOC__
__TOC__
Line 9: Line 14:


Now add a new file to your folder and name it ''register.js'' .
Now add a new file to your folder and name it ''register.js'' .
Then add the basic markup such as copyright, define, use strict and so on.
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.
In our tutorial the result looks like this.


Line 28: Line 33:
</pre>
</pre>


''Note: We need to use extensions so require the needed resources with  
''Note: We need to use extensions so we need to require the needed resources with
...['io.ox/core/extensions'], function (ext)...
  ['io.ox/core/extensions'], function (ext)
as seen above.''
as seen above.''
===Manifests===


Your file is not loaded yet. To do this we need to create a manifest file.
Your file is not loaded yet. To do this we need to create a manifest file.
Line 51: Line 58:
</pre>
</pre>


==Registering our plugin==
For further information about manifests look [https://documentation.open-xchange.com/latest/ui/customize/manifests.html here].
 
==Coding the base==
===Registering your plugin===


Now we need to register our plugin by extending the right extension point, which is ''io.ox/core/notifications/register'' .
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 and indexnumber and a register function.
Give your plugin a unique id, indexnumber and a register function.
Inside this register function we register our notification plugin at the controller and also giving it an id and our view, we create later on
Inside this function we register our notification plugin at the controller and also give it an id and our view, we create later on.
We do so by adding:
Do so by adding:


<pre class="language-javascript">
<pre class="language-javascript">  
//register our notification plugin
//register our notification plugin
ext.point('io.ox/core/notifications/register').extend({
ext.point('io.ox/core/notifications/register').extend({
Line 72: Line 82:
''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.
''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==
===Creating the View===


Since we use will use backbone to create our plugin it obviously needs a 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.
Call the variable like the one you gave to the controller to make it work.
In our exsample the code to create the view looks like this.
In our example the code to create the view looks like this.


<pre class="language-javascript">
<pre class="language-javascript">  
//the view of our plugin
//the view of our plugin
var NotificationsView = Backbone.View.extend({
var NotificationsView = Backbone.View.extend({
Line 96: Line 106:
</pre>
</pre>


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


==Adding the headline==
===Adding the headline===


Now we need 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.
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.
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.
Add this code to your views render method to create the point and invoke the draw method of its extensions.


<pre class="language-javascript">
<pre class="language-javascript">  
//build baton to wrap things up
//build baton to wrap things up
var baton = ext.Baton({ view: this });
var baton = ext.Baton({ view: this });
Line 112: Line 122:
</pre>
</pre>


''Note: Here we use our special baton objects to pass the data to the extension points.
''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.''
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.
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.
''this'' refers to our views dom node we draw in in the rendering method.


<pre class="language-javascript">
<pre class="language-javascript">  
//the header and container for the notification plugin
//the header and container for the notification plugin
ext.point('io.ox/core/notifications/tutorial/header').extend({
ext.point('io.ox/core/notifications/tutorial/header').extend({
Line 148: Line 157:
''Note: The notification area is loaded with a delay, so you have to wait a bit.''
''Note: The notification area is loaded with a delay, so you have to wait a bit.''


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


PICTURE
 
[[Image: header.png]]


Well done now we need to add some real notifications.
Well done now we need to add some real notifications.


==Listening for events==
==Adding Notifications==


Normally a notification area listens for events of the app it is related to. For exsample the mail notifications, listen for events on from the mail api.
===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.
For this tutorial we will just create a small dummy to create some Notifications for us and then trigger the proper event.
Line 163: Line 174:


<pre class="language-javascript">
<pre class="language-javascript">
//simple helper to trigger some events
/* simple helper to trigger some events
//normally this is done by mailapi, taskapi, etc.
  normally this is done by mailapi, taskapi, etc. */
var myEventTriggerer = {
var myEventTriggerer = {
         lookForItems: function () {
         lookForItems: function () {
Line 179: Line 190:
Then it triggers an event on itself and passes the array as an argument.
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.
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.
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.
This collection is available in the register method under ''notifications.collection''.
The controller looks for changes in this collection and triggers a redraw.
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.
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:
Remove our testing line ''notifications.collection.reset(new Backbone.Model());'' from the register method and add our new function:


<pre class="language-javascript">
<pre class="language-javascript">  
//fill our collection of notification models with new ones
/* fill our collection of notification models with new ones
//items here is an array of Objects containing our attributes
  items here is an array of Objects containing our attributes */
function reset(e, items) {
function reset(e, items) {
     var models = [];
     var models = [];
Line 200: Line 212:
</pre>
</pre>


These 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.
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 exsample.  
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.''
''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.
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.
We do this by adding this code to the register method.


<pre class="language-javascript">
<pre class="language-javascript">  
//now add the event listeners
//now add the event listeners
$(myEventTriggerer).on('set-tutorial-notification', reset);
$(myEventTriggerer).on('set-tutorial-notification', reset);
Line 216: Line 230:
</pre>
</pre>


==Drawing the Notifications==
===Drawing the Notifications===
Now that we have proper models, we need to draw them.
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.
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:
In our views render method we need to add:


<pre class="language-javascript">
<pre class="language-javascript">  
//loop over collection and draw
//loop over collection and draw
this.collection.each(function (model) {
this.collection.each(function (model) {
Line 233: Line 247:
Next we extend our extension point to draw the actual notification items.
Next we extend our extension point to draw the actual notification items.


In our exsample it looks like this:
In our example it looks like this:


<pre class="language-javascript">
<pre class="language-javascript">  
//draw a single notification item
//draw a single notification item
ext.point('io.ox/core/notifications/tutorial/item').extend({
ext.point('io.ox/core/notifications/tutorial/item').extend({
Line 251: Line 265:


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.
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 ad fill it with the title text of our model.
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.
Open up your Appsuite and check if everything works. Be sure to load the custom manifests if needed.
It should look like this:
It should look like this:


PICTURE
[[Image: notifications.png]]


==Adding a Sidepopup==
==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.
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.
Line 264: Line 279:
Change your views events so it looks like this.
Change your views events so it looks like this.


<pre class="language-javascript">
<pre class="language-javascript">  
//events from our items
//events from our items
events: {
events: {
Line 271: Line 286:
</pre>
</pre>


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.
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:
We will create the ''openPopup'' function now, add this function to your view:


<pre class="language-javascript">
<pre class="language-javascript">  
//the action for our sidepopup
//the action for our sidepopup
openPopup: function (e) {
openPopup: function (e) {
Line 296: Line 311:
</pre>
</pre>


This 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.
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.
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.
In the show method we draw the contents of our popup. In this case its the models title variable and description variable.
Line 302: Line 317:
Time to check if it works. In the appsuite your notifications should now open a sidepopup if you click on them.
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==
===Add a remove option===
We dont want our notifications to stay there forever, so we need a way to remove them.
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.
Let's add a button to do this.


Line 316: Line 331:
             .append(
             .append(
                 $('<div class="mytext">').text(baton.model.get('title')),//some text to fill it
                 $('<div class="mytext">').text(baton.model.get('title')),//some text to fill it
                 $('<button class="btn" data-action="close">').text('close')//close button
                 $('<button class="mybutton btn btn-primary" data-action="close">').text('close')//close button
             )
             )
         );
         );
Line 336: Line 351:
Finally add the close funktion to your view:
Finally add the close funktion to your view:


<pre class="language-javascript">
<pre class="language-javascript">  
//the action for the close button
//the action for the close button
closeNotification: function (e) {
closeNotification: function (e) {
Line 353: Line 368:
Congratulations your notifications should be removed if you click the button.
Congratulations your notifications should be removed if you click the button.


You can download the exsamplecode [[here|here]].
The final version should look like this.
 
[[Image: finished.png]]
 
==Download==
You can download the examplecode here.
 
[[File: Notification_tutorial.zip]].


[[Category:AppSuite]]
[[Category:AppSuite]]
[[Category:UI]]
[[Category:UI]]
== Stuck somewhere? ==
You got stuck with a problem while developing? OXpedia might help you out with the article about [https://documentation.open-xchange.com/latest/ui/miscellaneous/debugging.html debugging the UI].

Latest revision as of 10:02, 22 May 2017

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.