WebApp: Widgets

September 12, 2012 by Ivo van Doorn   Comments (0)

, , ,

Although plugins allow you to integrate new functionality into the WebApp, showing the user some information in a simple UI component in the 'Zarafa' tab or sidebar on the right might be quite useful as well. Think for example about the "Today's Appointments", "Facebook" or "Clock" widgets. Even more advanced functionality is possible where the user can play a game ("Shell game" widget) or create a new mail or appointment ("Quick Mail" and "Quick Appointment" widgets). In this Blogpost, we will go deeper into the Widget system, and how a widget can be created. To describe the functionality and API for the widget, we will be going to use the simplified version of the "Unread Mail" widget which was recently released.

Basic Widget

Creating a new widget is relatively easy. The WidgetClass that represents the Widget will have to extend the class `Zarafa.core.ui.widget.Widget`:

Zarafa.widgets.MailWidget = Ext.extend(Zarafa.core.ui.widget.Widget, {

    constructor : function(config) {

        config = config || {};

 

        Ext.applyIf(config, {

            name : 'mail',

            title : dgettext('plugin_mailwidget', 'Unread Mail')

        });

 

        Zarafa.widgets.MailWidget.superclass.constructor.call(this, config);

});

and the registration of the Widget to the  global `container` object has slightly different arguments, instead of an instantiated object the registration requires the class constructor:

Zarafa.onReady(function() {

    container.registerWidget(Zarafa.widgets.MailWidget, 'mail', dgettext('plugin_mailwidget', 'Unread Mail'), 'plugins/folderwidgets/resources/images/mail.png');

});

The second argument to the registerWidget function is the unique name of the widget, the last two argments are the display name and the path to the icon as should be shown in the Widget Dialog where the user can select the widget he would like to activate.

image

Settings

Some widgets might require the user to configure something, for the "Twitter Widget" that might be the number of results, for the "Facebook Widget" that is the URL to search for and for the "Appointment Widget" the refresh rate. These kinds of values are stored inside the settings for the user, on a per-widget instance basis. This means that when the user has two of the same widgets in his sidebar, each widget has its own settings. To access these settings, you do not have to use the `Zarafa.core.data.SettingsModel` directly. Instead, in your Widget, you can use the functions `set` and `get` together with the property name to request. For example:

initWidget : function()

{

    Zarafa.widgets.MailWidget.superclass.initWidget.apply(this, arguments);

 

    // Obtain the 'reloadinterval' from settings

    var interval = this.get('reloadinterval');

}

IMPORTANT: The `set` and `get` functions cannot be used before the `Zarafa.core.ui.widget.Widget` constructor has been called. If the settings are needed during the creation of the widget, they should be requested during the `initWidget` call as shown above.

Configuration

image

Now that it is clear how to obtain the settings, the user might still want to be able to change them in a userfriendly way. Configuration options are provided to user by clicking on the `gear` icon inside the widget titlebar. To enable this option for the user, the `config` object in the constructor should have the `hasConfig` configuration option set to true. When the user clicks on the 'gear' button, the function `config` in the widget will be called. This should create a new `Ext.Window` instance in which the UI for the configuration options should be shown:

config : function()

{

    var win = new Ext.Window({

        title : dgettext('plugin_mailwidget', 'Configure Widget'),

        width : 320,

        height: 200,

        items: [{

            xtype : 'form',

            items: [{

                xtype: 'zarafa.spinnerfield',

                fieldLabel: dgettext('plugin_mailwidget', 'Reload interval (ms)'),

                name: 'reloadinterval',

                minValue: 0,

                maxValue: 1800000,

                incrementValue: 1000,

                defaultValue: this.get('reloadinterval') || 300000,

                listeners: { 'change': this.onFieldChange, scope: this },

                plugins: ['zarafa.numberspinner']

            }],

            buttons : [{

                text : dgettext('plugin_mailwidget', 'Close'),

                handlers: function() {

                   win.close();

                },

               scope: this

            }]

        }]

    });

 

    win.show();

},

 

onFieldChange : function(field, newValue, oldValue)

{

    this.set(field.getName(), newValue);

}

The code above will open a new Ext.Window instance, where the user can control the reload interval option. As soon as the user starts changing the value, we directly apply the value to the settings using the `set` function.

Loading the data

The examples given above are already sufficient to get you started on the development of a widget, all code which you can place inside the Widget is ExtJs specific, and doesn't rely on the API of the Zarafa WebApp. Just to make the example widget a bit more interesting, we are now going add some additional functionality to show the contents of your inbox folder. The first step is to obtain the default Inbox folder and load the contents into a `Zarafa.mail.MailStore`, at the moment the Widget is instantiated we are not 100% sure if the hierarchy was already loaded or not. Hence we have to apply a small trick to the widget:

initWidget : function()

{

    Zarafa.widgets.MailWidget.superclass.initWidget.apply(this, arguments);

 

    // Wait for the hierarchy store to be loaded.

    var hierarchyStore = container.getHierarchyStore();

    this.mon(hierarchyStore, 'load', this.onHierarchyLoad, this);

 

    // needed when adding the widget after hierarchy load

    this.onHierarchyLoad(hierarchyStore);

},

 

onHierarchyLoad : function(hierarchyStore)

{

    this.folder = hierarchyStore.getDefaultFolder('inbox');

    if (this.folder) {

       this.store.load({ folder : this.folder });

    }

},

This ensures that immediately when the hierarchy is loaded, the store will be loaded with all the mail items from the given folder. Please refer to the `ListModuleStore` API documentation for additional filtering options, where either the amount of data as loaded from the server can be reduced (using the `setRestriction` function) or the amount of displayed data is reduced (using the `filterBy` function).

Showing the data

Now that we have obtained the data, we want to display it inside a Grid component. Inside the constructor we can add the GridPanel to the interface:

constructor : function(config) {

    config = config || {};

    this.store = new Zarafa.mail.MailStore();

 

    Ext.applyIf(config, {

        name : 'mail',

        title : dgettext('plugin_mailwidget', 'Unread Mail'),

        items: [{

            xtype : 'zarafa.gridpanel',

            store : this.store,

            loadMask : {

                msg : dgettext('plugin_mailwidget', 'Loading mail') + '...'

            },

            viewConfig: {

                deferEmptyText: false,

                emptyText: '<div class="emptytext">' + dgettext('plugin_mailwidget', 'No unread mail.') + '</div>',

                forceFit: true

            },

            colModel : new Ext.grid.ColumnModel({

                columns: [{

                    header: dgettext('plugin_mailwidget', 'From'),

                    dataIndex: 'sent_representing_name',

                    menuDisabled : true,

                    renderer: Ext.util.Format.htmlEncode

                },{

                   header: dgettext('plugin_mailwidget', 'Subject'),

                   dataIndex: 'subject',

                   editable: false,

                   menuDisabled : true,

                   renderer : Ext.util.Format.htmlEncode

                },{

                   header: dgettext('plugin_mailwidget', 'Received'),

                   dataIndex: 'message_delivery_time',

                   editable: false,

                   menuDisabled : true,

                   renderer : Zarafa.common.ui.grid.Renderers.datetime

                }]

            })

        }]

    });

 

    Zarafa.widgets.MailWidget.superclass.constructor.call(this, config);

});

This will create a grid (an instance of `Zarafa.common.ui.grid.GridPanel`), which will show a load mask when the store is busy loading the mail from the server. When no mail was found in the folder, then the message 'No unread mail' will be shown to the user. Finally the Ext.grid.ColumnModel describes which columns should be shown to the user, currently we will be showing the "From", "Subject" and "Received" columns. For formatting of the contents of the Columns, please refer to the `Ext.grid.Column` API documentation.

Final thoughts

Although we didn't show the exact implementation of the "Unread Mail" widget, this blogpost has hopefully given enough inspriration on the creation of Widgets, and how they can be used to display information that is of interest to the user. Widgets can be made as simple or as complex as the developer wishes. They might obtain data from the Zarafa system, like the widget in this example, or obtain them from them from another website like the "Facebook" and "Twitter" widgets.

Links