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

From Open-Xchange
No edit summary
No edit summary
Line 5: Line 5:
==Preparations==
==Preparations==


To start a new plugin for the notification area you have to add a new folder at apps.plugins/notifications/ .
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/ .
For this tutorial we will create ''plugins/notifications/tutorial/'' .


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'' .
Now add the basic markup such as copyright define, use strict and so on.
Then add the basic markup such as copyright, define, use strict and so on.
Our tutorial result should look similar to this.
In our tutorial the result looks like this.


CODE
<pre class="language-javascript">
/**
/**
  * your copyright here
  * your copyright here
Line 26: Line 26:
     return true;
     return true;
});
});
CODE
</pre>


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


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.
Create a new file in your folder with the name manifest.json with the following code in it:
Create a new file in your folder with the name ''manifest.json'' with the following code in it:
 
<pre class="language-javascript">


CODE
{
{
namespace: "io.ox/core/notifications"
namespace: "io.ox/core/notifications"
}
}
CODE
</pre>


For developing add the following code to src manifests.js
For developing add the following code to ''src/manifests.js''
CODE
 
<pre class="language-javascript">
{
{
     namespace: ['io.ox/core/notifications'],
     namespace: ['io.ox/core/notifications'],
     path: 'plugins/notifications/tutorial/register'
     path: 'plugins/notifications/tutorial/register'
}
}
CODE
</pre>


==Registering our plugin==
==Registering our plugin==


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


CODE
<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 66: Line 68:
     }
     }
});
});
CODE
</pre>


io.ox/tutorial is the id the controlelr 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==
Line 76: Line 78:
In our exsample the code to create the view looks like this.
In our exsample the code to create the view looks like this.


CODE
<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 92: Line 94:
     }
     }
});
});
CODE
</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 this soon.''


==Adding the headline==
==Adding the headline==
Line 103: Line 105:
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.


CODE
<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 });
//draw header and container by creating an extension point and invoke drawing on its extensions
//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);
ext.point('io.ox/core/notifications/tutorial/header').invoke('draw', this.$el.empty(), baton);
CODE
</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.


CODE
<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 126: Line 128:
     }
     }
});
});
CODE
</pre>


Congratulations the first steps are done. Time for some testing to see if we did it right.
Congratulations the first steps are done. Time for some testing to see if we did it right.
Line 134: Line 136:
Now we want to see how it looks in the program.
Now we want to see how it looks in the program.
To do this add this line of code to your register method:
To do this add this line of code to your register method:
CODE
 
<pre class="language-javascript">
notifications.collection.reset(new Backbone.Model());
notifications.collection.reset(new Backbone.Model());
CODE
</pre>
 
This creates an empty notification model and adds it to our plugins collection.
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.
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.
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.
''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 its loaded you should see something like this:
Line 154: Line 158:
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.
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.


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.


Our dummy looks like this:
Our dummy looks like this:


CODE
<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.
Line 170: Line 174:
         }
         }
     };
     };
CODE
</pre>


This dummy creates an array with two objects containing our notifications data.
This dummy creates an array with two objects containing our notifications data.
Line 176: Line 180:


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 identyfy 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:


CODE
<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
Line 194: Line 198:
     notifications.collection.reset(models);//fill the collection
     notifications.collection.reset(models);//fill the collection
     }
     }
CODE
</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.
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.
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 exsample.  


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.''


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.
CODE
 
<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 209: Line 214:
//just to make sure it gets filled with values on load we trigger our search method
//just to make sure it gets filled with values on load we trigger our search method
myEventTriggerer.lookForItems();
myEventTriggerer.lookForItems();
CODE
</pre>


==Drawing the Notifications==
==Drawing the Notifications==
Line 216: Line 221:
In our views render method we need to add:
In our views render method we need to add:


CODE
<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 223: Line 228:
         .invoke('draw', this.$('.notifications'), baton);//draw the item
         .invoke('draw', this.$('.notifications'), baton);//draw the item
}, this);
}, this);
CODE
</pre>


This time we draw in the container div we created by our header drawing method.
This time we draw in the container div we created by our header drawing method.
Line 229: Line 234:


In our exsample it looks like this:
In our exsample it looks like this:
CODE
 
<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 242: Line 248:
     }
     }
});
});
CODE
</pre>


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 ad fill it with the title text of our model.


Line 258: Line 264:
Change your views events so it looks like this.
Change your views events so it looks like this.


CODE
<pre class="language-javascript">
//events from our items
//events from our items
events: {
events: {
     'click .item': 'openPopup',
     'click .item': 'openPopup',
},
},
CODE
</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:


CODE
<pre class="language-javascript">
//the action for our sidepopup
//the action for our sidepopup
openPopup: function (e) {
openPopup: function (e) {
Line 288: Line 294:
     });
     });
}
}
CODE
</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.
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.
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 308:
Add the button in your item draw function:
Add the button in your item draw function:


CODE
<pre class="language-javascript">
ext.point('io.ox/core/notifications/tutorial/item').extend({
ext.point('io.ox/core/notifications/tutorial/item').extend({
     draw: function (baton) {
     draw: function (baton) {
Line 315: Line 321:
     }
     }
});
});
CODE
</pre>


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


CODE
<pre class="language-javascript">
events: {
events: {
     'click .item': 'openPopup',
     'click .item': 'openPopup',
     'click [data-action="close"]': 'closeNotification'
     'click [data-action="close"]': 'closeNotification'
}
}
CODE
</pre>


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.
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.
Line 330: Line 336:
Finally add the close funktion to your view:
Finally add the close funktion to your view:


CODE
<pre class="language-javascript">
//the action for the close button
//the action for the close button
closeNotification: function (e) {
closeNotification: function (e) {
Line 340: Line 346:
     this.collection.remove(model);//remove it from the collection
     this.collection.remove(model);//remove it from the collection
}
}
CODE
</pre>


Again get the cid to identify the right model, then 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.
''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.
Congratulations your notifications should be removed if you click the button.


You can download the exsamplecode here.
You can download the exsamplecode here.

Revision as of 13:46, 11 April 2013

Writing a plugin for the notification area

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, 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 require the needed resources with ...['io.ox/core/extensions'], function (ext)... as seen above.

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'
}

Registering our plugin

Now we need to register our 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. 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 We 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 exsample 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 this soon.

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.

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 its loaded you should see something like this:

PICTURE

Well done now we need to add some real notifications.

Listening for events

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.

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.

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
    }

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. Functions to add and remove models are done the same way but are not always needed, as in this exsample.

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

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 exsample 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 ad 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:

PICTURE

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')));
            });
    });
}

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. 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 dont 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="btn" 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.

You can download the exsamplecode here.