Creating custom widgets for use in the dashboard

To use custom widgets in the WSRR dashboard, you must create XML and JavaScript files, and package them so that you can load them into the dashboard.

Before you begin

To develop widgets, you must be familiar with the following concepts:
  • Dojo 1.9
  • Extensible Markup Language (XML)
  • HyperText Markup Language (HTML)
  • iWidget 2.1 specification
  • JavaScript
  • front-end web development
Depending on the function of the widgets that you are developing, you might also need to know:
  • Java™ 2 Platform, Enterprise Edition (J2EE)
  • Representational State Transfer (REST)
Your widget implementation files must form one or more Asynchronous Module Definition (AMD) modules that are supported by Dojo V1.7 or later.
Important: In the WSRR V8.5.6 dashboard, the instance of the IBM® Dojo Toolkit is based on version 1.9 of the Dojo toolkit. However, this bundled version might be updated as needed over time. Updates might include entire new Dojo versions and specific defect fixes. Compatibility of future Dojo versions is defined by the Dojo project.
To create a custom widget that you can run in the WSRR dashboard, it is recommended that you create three files: an XML file and two JavaScript files. Before you create these files, you can optionally create a directory structure to contain the files for your custom widget. You can use basic tools such as a text editor or other editors to create custom widgets. A set of sample files are provided for download from this link: Download MySampleWidget.zip. You can extract the contents of the .zip file, and then review and customize the samples to help you create your own custom widgets, as documented in the subtasks in this topic. The .zip file contains the following samples:
  • iWidget.xml: An XML file that defines a widget
  • YourThumbnailImage.png: A thumbnail representation of the widget, which is specified in iWidget.xml
  • MyCustomWidget.js: A JavaScript file that defines an iWidget implementation (that is, the business logic of the widget)
  • MyCustomWidgetNotAMD.js: A JavaScript file that defines an iScope implementation/handler, which loads MyCustomWidget.js

    The .js files are located in a sample/custom/widget subdirectory.

Note:
  • To run widgets on the dashboard, some elements must be specified exactly as shown.
  • Custom widgets do not support automatic sizing. Therefore, in the Resize menu, the Automatically set widget height option will not be displayed.

Creating the XML file

About this task

A widget is defined by an iWidget XML file. Create the iWidget XML file and define your widget in the file. For more information about the widget definition and its elements, see the iWidget 2.1 specification.
Tip: An iWidget XML sample file (iWidget.xml) is provided in the sample .zip file. The XML file contents correspond to the examples shown in the following procedure. You can use this file as a starting point for defining your own widget, and follow the documented steps to customize the file as required.

Procedure

In the widget definition and its elements, you must define the following items:

  • The iScope and schemas
  • The modes that the widget supports
  • The path and name of the JavaScript file that declares the iScope
  • The attributes of the widget
  • The events
  • Any files to which the widget might refer
Note: The order in which you define the parts of the widget is not important, but the steps in this task are shown in the order of the items in the list.

  1. Define the iScope, which is the Dojo wrapper class for the widget implementation, and the required schemas. For example:
    <iw:iwidget
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    	xmlns:iw="http://www.ibm.com/xmlns/prod/iWidget"
    	iScope="sample.custom.widget.MyCustomWidgetNotAMD" 
    The iScope value must match the name of the JavaScript class that defines the behavior of the widget.
  2. Define the modes that the widget supports and the definitions for each mode by using the supportedModes parameter. Supported modes are view and edit. Separate each mode by using a space.
    <iw:iwidget
    	...
    	supportedModes="view edit" 
  3. Specify the path and name of the JavaScript file that declares the iScope. The iScope is a Dojo class that is an entry point to an iContext class at run time. This entry point is the part of the widget that interacts with the environment through the iContext class. The iContext class is central to the widget runtime environment and provides all the environmental services such as access to global variables, a shared state, local variable storage, widget communication that use events, remote services, mode support, and many other capabilities. For example:
    <iw:resource src="sample/custom/widget/MyCustomWidgetNotAMD.js" />

    In this case, the file is in a different directory from the iWidget XML file and so you need to include the path.

  4. Optional: Specify a title, a description, and a default height for your custom widget, as shown in the following example. This part of the widget is specified by the id parameter with the value idescriptor, which you can also use to provide a thumbnail image. The value of the thumbnail item is a URI that is relative to where the iWidget XML file is located.
    <iw:itemSet id="idescriptor">
    	<iw:item id="title" lang="en" value="My Custom iWidget"></iw:item>
    	<iw:item id="description" lang="en" value="Your Custom iWidget"></iw:item>
    	<iw:item id="defaultHeight" lang="en" value="250"></iw:item>
    	<iw:item id="thumbnail" value="YourThumbnailImage.png"></iw:item>
    </iw:itemSet>
  5. Define the attributes of the widget, which include the settings that users can modify. These attributes provide the default values for the widget. The values are stored as strings, therefore your implementation might need to convert these values. For example:
    <iw:itemSet id="attributes" private="true">
    	<iw:item id="url" readOnly="false" value="http://www.ibm.com"/>
    </iw:itemSet>
  6. Specify the events that the widget publishes and handles by adding a definition and description for each event:
    <iw:event id="com.ibm.sr.ui.iwidgets.events.itemSelected" handled="true" published="false" onEvent="loadData" eventDescName="desc_itemSelected"/>
    <iw:eventDescription 
    	id="desc_itemSelected" 
    	lang="en" 
    	payloadType="json" 
    	title="Item selected" 
    	description="item selected">
    </iw:eventDescription>
  7. Define the view mode so that the dashboard has something to display. If you want users to modify the content of the widget in some way, which a user can do by using the Edit settings menu item, add the edit mode. To add content to modes that you added after you have defined the mode, specify content details. For example:
    <iw:content mode="view">
    	<![CDATA[
    	<div id="_IWID_viewRoot">
    	</div>
    	]]>
    </iw:content>
  8. Add, as resources, the relative path and name of any other files that your widget needs. For example, if your widget uses a .css file for formatting, add the path and name of this file as a resource. However, when you are adding resources, consider that too many requests for resources impacts performance and the code should refer to as few JavaScript, CSS, and image files as possible. Consider using techniques such as image spriting, combining and minimizing JavaScript and CSS files, and lazy loading of resources (such as waiting to load resources for edit mode until the onEdit event occurs) when you design your widget.

Creating the JavaScript files

About this task

Create a JavaScript file that declares the iScope and then define the iScope by identifying its interface. Create the implementation for your widget by using JavaScript. Continue to develop the iScope in parallel with developing the widget implementation.

It is recommended that you use separate JavaScript files for your widget implementation and the iScope implementation. The iScope implementation loads the widget implementation file using the Dojo Asynchronous Module Definition (AMD) loader and defers all iWidget handlers to the widget implementation file.

Tip: Two JavaScript sample files that define an iScope implementation (MyCustomWidgetNotAMD.js) and a widget implementation (MyCustomWidget.js) are provided in the sample .zip file within a sample/custom/widget subdirectory. The contents of these files correspond to the examples shown in the following procedure. You can use these files as a starting point for defining your own implementations, and follow the documented steps to customize the files as required.

Procedure

  1. Create the JavaScript iScope implementation with handlers for the modes that you added in the iWidget definition and for the various events that are defined in the interface, including the predefined events from the iWidget specification. For the following predefined iEvents (from the iWidget specification), the dashboard provides default event handlers, which you can override if needed:
    onLoad
    Is called when the widget loads for the first time and when the browser refreshes. The widget can initialize the initial view in this handler. The iScope implementation should load the widget implementation in this handler and pass it a reference to the iWidget. There is no event payload. You can retrieve the item values by using code similar to these examples: var att = this.iContext.getiWidgetAttributes(), and this.name = att.getItemValue("name").
    For example, if the widget implementation is in the sample.custom.widget.MyCustomWidget class, the onLoad method is:
    onLoad: function() {
    		 // get location of the sample directory
    		 var root = this.iContext.io.rewriteURI("sample");
    		 
    		 var context = this;
    		 require(["dojo/Deferred"], function(Deferred) {
    		 		 context.def = new Deferred();
    		 		 require({
    		 		 		 packages: [
    		 		 		 		 {name: "sample", location: root}
    		 		 		 ]
    		 		 }, ["sample/custom/widget/MyCustomWidget"], function(MyCustomWidget) {
    		 		 		 try {
    		 		 		 		 if (!context.isUnloaded) {
    		 		 		 		 		 context.amdModule = new MyCustomWidget();
    		 		 		 		 		 context.amdModule.onLoad(context);
    		 		 		 		 }
    		 		 		 
    		 		 		 		 context.def.resolve();
    		 		 		 } catch(e) {
    		 		 		 		 console.log(e);
    		 		 		 }
    		 		 });
    		 });
    },
    In the widget implementation file, the onLoad method is passed a reference to the iWidget:
    onLoad:function(iWidget) {
    		 try {
    		 		 // store the iWidget reference
    		 		 this.iWidget = iWidget;
    		 		 
    		 		 console.log("onLoad");
    		 } catch(e) {
    		 		 console.log(e);
    		 }
    },
    onReload
    Is called when the widget is reloaded. This event handler is similar to onLoad, however, this handler is called in slightly different circumstances. When a user is in edit mode and selects one of the options at the bottom of the Settings window, the onReload event handler is called when the view mode is refreshed. This is slightly different behavior than the iWidget specification calls for, because the specification specifies that this event must fire before the reload, rather than during an edit mode refresh of the iWidget content. There is no event payload.
    onUnload
    Is called when the widget is about to be unloaded. There is no event payload.
    Important: Ensure that all Dojo widgets that you create are cleaned up during this event handler. You must call destroyRecursive() to ensure that all child Dojo widgets are cleaned up in terms of memory usage.
    onRefreshNeeded
    Is called when the iContext determines that the data in the widget is stale. If applicable, this event signals to the iWidget to refresh its data.
  2. Create a mode handler for each mode specified in the supportedModes attribute in the widget definition. For example, if your widget has a view mode and an edit mode, create onView and onEdit methods.
    In the iScope implementation, the onView method should wait for the load of the iWidget implementation, and then defer to the onView method defined in the iWidget implementation. In the iScope implementation, the onView method can be coded as shown:
    onView: function(iEvent) {
    		 var context = this;
    		 this.def.then(function() {
    		 		 if (!context.isUnloaded) {
    		 		 		 context.amdModule.onView(iEvent);
    		 		 }
    		 }, function(err) {
    		 		 console.log("ERROR: " + err);
    		 });
    },
    In the iWidget implementation, the onView method can be coded as shown:
    onView: function(){
    	... 
    }
  3. Create a handler for the onSizeChanged event in both the iScope and iWidget implementation. The event has a payload that contains values for the newWidth and newHeight attributes. The handler uses this information to resize the widget to the specified width and height. If the user has minimized the widget, these attributes have a value of 0. The following code shows how to use the onSizeChanged handler in the iScope implementation to defer to the iWidget implementation:
    onSizeChanged: function(iEvent) {
    		 if (this.amdModule) {
    		 		 this.amdModule.onSizeChanged(iEvent);
    		 }
    },
    The following code shows how to use the onSizeChanged handler in the iWidget implementation:
    onSizeChanged:function(event) {
    		 try {
                            ..
    		 }catch(e) {
    		 		 console.log(e);
    		 }
    },
  4. Optional: If your widget is accessing data through REST APIs, use Uniform Resource Identifiers (URIs) in code, as shown in the following example:
    var def = xhr(this.iContext.io.rewriteURI(uri), {
    	   method: "GET",
    }, true);
    def.then(successFunc, function(err) {
            errorFunc(err, def.response);
    });
    You can use a similar approach for HTTP actions such as PUT, POST, and DELETE.

    The variable def is a Dojo Deferred object. For more information on Deferred objects, read the dojo/Deferred article on the Dojo website.

    The statement this.iContext.io.rewriteURI(uri) will generate a URL that uses the Ajax proxy if the uri refers to a website that is different from the site which is serving the JavaScript. For information about configuring the proxy, see Configuring the Ajax proxy.

  5. Optional: If your custom widget uses images or other files that the browser must retrieve then you must use the getResourceUrl method provided by the CustomWidgetHelper to generate an absolute URL that the browser can use. The getResourceUrl method accepts the iContext class and a URI relative to your iWidget XML file. See the reference topic CustomWidgetHelper.js helper for custom widgets for details. You must set the <src> attribute of an <img> tag programmatically by using the return value from the getResourceUrl method.

    The following example references the file myImageFile.png in a directory named images. The directory is in the same location as the iWidget XML file.

    var myWidgetImage = document.createElement("img");
    var myWidgetImageUrl = this.customWidgetHelper.getResourceUrl(this.iContext,"./images/myImageFile.png");
    myWidgetImage.setAttribute("src", myWidgetImageUrl);

Packaging

Procedure

  1. Optional: Create image files to serve as the preview and icon images for your custom widget.
    Note: If you specify a thumbnail in your idescriptor, the image should be 160 pixels wide by 125 pixels high to avoid the browser scaling the image within the dashboard panels.
  2. Package your iWidget XML file, widget implementation files, and image files into a .zip file.
    Note: The path to the iScope widget implementation file within your .zip file must match the package name of your iScope that is declared in your iWidget XML file. For example, if your iScope is sample.custom.widget.MyCustomWidgetNotAMD, then MyCustomWidgetNotAMD.js must exist in the following directory path inside the .zip file: sample/custom/widget. If your iWidget implementation is sample.custom.widget.MyCustomWidget, then MyCustomWidget.js must exist in the following directory path inside the .zip file: sample/custom/widget.

What to do next

Load the .zip file that you created by using the dashboard. See Managing custom widgets.

The CustomWidgetHelper is used in the example JavaScript code to illustrate how the WSRR REST endpoint is retrieved. To read about the CustomWidgetHelper and what it can be used for, see the CustomWidgetHelper topic.

Debugging

To debug your custom iWidget, it is recommended that you use the JavaScript debugger in a web browser to step through the code and set breakpoints.

Procedure

  • If you followed the recommendations for creating a custom widget, you should have two JavaScript files: one file that defines the iScope and which is loaded using Dojo synchronous loading, and another file that defines the implementation of the methods in the iScope and which is loaded using the Dojo asynchronous module definition loader. Because of the way in which the iWidget container works, the JavaScript file that defines the iScope will not appear in the web browser debugger source file list, but the JavaScript file that defines the implementation of the methods in the iScope will appear and can be debugged easily.
  • Use a try...catch statement in all public methods on the iWidget implementation, to print the error to the console, because the underlying widget container will not output any errors it receives. You can also then set a breakpoint on the line, which prints the error, in order to further debug the error.

Example: Using a try...catch statement

The following example shows the code for an onSizeChanged method that uses a try...catch statement:
onSizeChanged:function(event) {
		 try {
		 		 var newWidth = event.payload.newWidth; 
		 		 var newHeight = event.payload.newHeight; 
		 
		 		 //TODO Any resizing code, once/if it is needed 
		 }catch(e) {
		 		 console.log(e);
		 }
},