Create high-quality iWidgets

A practical guide

This article explores best practices for developing high-quality iWidgets. Learn about the development environment, directory layout, namespace issues, using dijit, switching modes, item set, globalization, resizing, and the build process. Once you know the simple iWidget component model, you can use different iWidgets to create powerful mashups.

Share:

Fu Cheng (chengfbj@cn.ibm.com), Software Engineer, IBM China

Fu Cheng photoFu Cheng is a member of the IBM Mashup Center development team at the IBM China Development Lab. He worked on IBM Mashup Center 1.1 and 2.0. Fu received a master's degree in Computer Science from Peking University.



31 August 2010

Also available in Chinese Japanese

Introduction

The iWidget is an IBM specification that provides a standard definition for a widget, allowing for seamless interoperability across various platforms and products. An iWidget is a browser-based component model that encapsulates web content and can participate in content presentation frameworks. It acts as a wrapper for any web content that you create.

iWidgets and IBM

iWidgets are used in many IBM products, including IBM Mashup Center, IBM WebSphere® Portal, and IBM Lotus® Connections. Download a trial version of IBM Mashup Center and IBM WebSphere Portal Express.

In this article, learn several best practices for developing iWidgets. It's common to use JavaScript libraries in iWidgets. Dojo is used in this article. Most of the best practices still apply if other JavaScript libraries are used, however. IBM Mashup Center is used as the target runtime to display iWidgets.

You can download and deploy a sample iWidget to IBM Mashup Center to see how it works (see the Download table below).

This article assumes you have a basic understanding of developing iWidgets. See Resources for more information.


Setting up the environment

Before you can develop iWidgets you need to set up the appropriate development environment. A good development environment can increase productivity. The development environment consists of:

  • A build-time environment to write the source code of iWidgets

    The build-time environment depends on the server-side implementation technique used for the iWidget. You can choose your favorite IDE according to the server-side programming language. If Java EE™ is used, the recommended IDE is Eclipse with WTP (Web Tools Platform) plug-ins. You can use Apache Tomcat as the web container.

  • An iWidget runtime to initialize iWidgets and see how they work for end users

    The best iWidget runtime is IBM Mashup Center, where it is very simple to add and view iWidgets. There are two ways to add iWidgets to IBM Mashup Center:

    • Add an iWidget package in WAR or zip format.

      When a package is used, the iWidget is actually hosted on IBM Mashup Center.

    • Add the URL of an iWidget's definition XML file.

      Using the URL iWidget's definition XML is a better choice for development. Once changes have been made to the iWidget, you don't have to package and deploy the iWidget again as long as the URL is not changed.

See Resources for links to get the tools mentioned above.


Creating an iWidget

Once the development environment is ready, you can start to create new iWidgets. Use the following steps:

  1. Create a Dynamic Web Project in Eclipse for an iWidget.
  2. Create the iWidget's definition XML file that contains minimum content only.
  3. Deploy the project to Apache Tomcat using Eclipse WTP, and start the server.
  4. Get the URL of the iWidget's definition XML and add it to IBM Mashup Center.
  5. View the iWidget in IBM Mashup Center.
  6. Continue to edit the iWidget and view the result.

After you complete the steps above, you can add more content to the project.

An XML file that conforms to the iWidget specification is the only requirement of an iWidget. The XML file can contain inline HTML, CSS, and JavaScript code. It's possible to contain all the HTML, CSS, and JavaScript code in the XML file, but it makes the code messy and hard to maintain. Using inline HTML, CSS, and JavaScript code is only recommended for small iWidgets or as the output of a build process. (Read more about a build process later in this article.)

For iWidgets that have complicated logic, HTML, CSS, and JavaScript code should be separated into different files.

Figure 1 shows the basic directory layout of a typical iWidget. Files of client-side code are placed under the WebContent folder. JavaScript and CSS files are placed under the js folder and css folder, respectively.

Figure 1. Basic directory layout of an iWidget
Basic directory layout of an iWidget, showing all directories and subdirectories

Avoiding namespace pollution

An iWidget may expose some global variables. When multiple iWidgets have been added to the same page, they should not conflict with each other. An iWidget should make sure the global namespace is not polluted.

The iWidget's scope object needs to be visible in the global object. Choose a good name for it. For example, a name such as com.example.widget.myWidget.MyWidget is better than MyWidget since short names are more likely to cause collision. All other JavaScript objects should be kept in the same namespace.

It is recommended that you add a special CSS class name to the root node of each mode, for example: com_example_widget_myWidget. This class name should be prepended to all the selectors used in the iWidget's CSS file. Listing 1 shows some sample CSS declarations.

Listing 1. Sample CSS declarations of an iWidget
.com_example_widget_myWidget {

}

.com_example_widget_myWidget .header {

}

.com_example_widget_myWidget .body {

}

Another kind of global name that is easily ignored is the topic name used by dojo.publish() and dojo.subscribe(). There may be multiple instances of the same iWidget existing on the same page. If all these instances subscribe to the same topic, it can cause serious problems. For example, imagine an iWidget that manages items of a logged-in user. One instance publishes a topic to delete an item for a user. Other instances can receive this topic and can delete the items of other users. To solve the potential problem, the unique ID of an iWidget instance must be part of the topic name. The iWidget instance's ID can be retrieved using this.iContext.widgetId. A good example of a topic name is com/example/widget/myWidget/deleteItem/{widgetId}.


Using dijit

An iWidget can have different modes. Each mode has its own structure, style, and behavior. You can write HTML fragment code for each mode in the definition XML file directly. Listing 2 below shows a code snippet of an iWidget's view mode. The special variable IWID will be replaced with the iWidget's unique ID after the iWidget is loaded.

Listing 2. Write HTML code of a mode in the definition XML file
<iw:content mode="view">
    <![CDATA[
        <div>
            <input id="_IWID_hobby" type="text">
            <button id="_IWID_addButton">Add</button>
        </div>
    ]]>
    </iw:content>

This approach is only suitable when the mode's content is simple; otherwise, it's difficult to implement and maintain.

The recommended approach is to create a dijit for each mode. For example, if the iWidget supports both view and edit mode, a dijit is created for the view mode and another dijit is created for the edit mode. The benefit of this approach is that it's much simpler for certain tasks when dijits are used.

When developing iWidgets you might want to:

  • Find certain DOM nodes and manipulate them. If a mode's HTML fragment is declared directly in the iWidget's definition XML file, an ID should be assigned to the DOM node, and dojo.byId() should be used to find the node. While in a dijit, you can use dojoAttachPoint to declare DOM nodes visible to the dijit's JavaScript object.
  • Bind event listeners to certain DOM nodes. If dijits are not used, the DOM nodes should be found first, and then dojo.connect() is used to bind event listeners. dojo.disconnect()is also needed to remove the listeners when the iWidget is unloaded. In a dijit, you can use dojoAttachEvent to bind event listeners declaratively.
  • Add globalization support by using Dojo's dojo.i18n module (when dijits are used). See the Globalization section of this article for more details.

The dijits used in an iWidget can be created declaratively or programmatically. If dijits are declared in the iWidget's definition XML file, you must invoke dojo.parser.parse() to initialize those dijits after the iWidget is loaded. It should be done only once for each mode. The better choice is to create dijits programmatically.

Listing 3 and Listing 4 provide the basic HTML and JavaScript code skeletons for using dijits in iWidgets.

Listing 3. Basic HTML code skeleton for using dijits
<iw:iwidget name="sampleWidget" iScope="com.example.sampleIWidget.SampleWidget" 
  supportedModes="view edit" mode="view"
  xmlns:iw="http://www.ibm.com/xmlns/prod/iWidget">
    
    <iw:content mode="view">
        <![CDATA[
            <div id="_IWID_viewRoot" class="com_example_sampleIWidget">
                 
            </div>
        ]]>
    </iw:content>
    
    <iw:content mode="edit">
        <![CDATA[
            <div id="_IWID_editRoot" class="com_example_sampleIWidget">
                
            </div>
        ]]>
    </iw:content>
    
</iw:iwidget>
Listing 4. Basic JavaScript code skeleton for using dijits
onview : function() {
    if (!this._viewMode) {
        var viewRoot = this.byId("viewRoot");
        var node = dojo.create("div", {}, viewRoot, "only");
        this._viewMode = new sampleIWidget.widget.ViewMode({}, node);
    }
},
		
onedit : function() {
    if (!this._editMode) {
        var editRoot = this.byId("editRoot");
        var node = dojo.create("div", {}, editRoot, "only");
        this._editMode = new sampleIWidget.widget.EditMode({}, node);
    }
},  

onUnload : function() {
    if (this._viewMode) {
        this._viewMode.destroyRecursive();
        this._viewMode = null;
    }
    if (this._editMode) {
        this._editMode.destroyRecursive();
        this._editMode = null;
    }
}

As shown in the two listings above, the HTML fragment of view mode and edit mode is very simple. It contains only one DOM node as the containers of the mode's content. In the onview and onedit functions of iWidget's scope object, dijits are created programmatically. A new node is created first as the only child of the container node. The new node makes destroying the dijit easier since the mode's original DOM structure is kept after destroying the dijit. Be sure to destroy the dijits when the iWidget is unloaded from the page. Use a dijit's destroyRecursive() in the iWidget's onUnload() function to destroy it.


Handling mode switches

An iWidget at a minimum has the view mode. Most iWidgets also have the edit mode to allow end users to customize the iWidget's behavior. The user can switch between different modes. When the iWidget has been switched to view mode or edit mode, the corresponding function, onview() or onedit(), will be invoked. Keep in mind that onview() and onedit() may be invoked multiple times.

If the default mode is view mode, after the iWidget is loaded onview() will be invoked. When the user switches to edit mode, onedit() is invoked. Once the user has finished the edit and goes back to the view mode, onview() is invoked again.

If the user made changes in edit mode, the changes should be reflected in view mode. One approach is to recreate the view mode again from scratch, which means the dijit is destroyed and initialized again. This approach is possible because the changes have been stored into the iWidget's item set. (Read more about item set later in this article.) It is simple but slow, especially when the user interface and logic are complicated.

Another approach is to partially update, and not recreate, the dijits. This approach is complicated to implement but has better performance. In an extreme example, if the user does nothing in the edit mode and the former approach is used, then the whole user interface is recreated unnecessarily.

iWidget developers must choose from the two approaches above. Listing 5 shows an example of using partial update to handle mode switching.

Listing 5. Partially update a mode's content
                //In edit mode
var changeSummary = {...};
dojo.publish("com/example/sampleIWidget/editFinished/{widgetId}", [changeSummary]);

//In view mode
dojo.subscribe("com/example/sampleIWidget/editFinished/{widgetId}", 
    dojo.hitch(this, function(changeSummary) {
    //Use changeSummary to update the user interface
    this._toViewMode();
}));

Basically, the user's changes are collected in edit mode and a JSON object is used to represent these changes. If the user decides to save the changes, this JSON object is passed to the view mode and the view mode is updated according to the content in this JSON object.


Using item set

Item set is persistence storage that an iWidget can use to store user preferences and internal data. If data needs to be persistent and should be available when the iWidget is loaded next time, it should be stored in the item set. For example, a weather forecast iWidget needs to store the city name the user has selected. Standard APIs can be used to set and get data from the item set.

An item set is a collection of items. Each item is a name-value pair. Item set itself is very simple to use.

Use a single JSON object

Some iWidgets might have a lot of configuration values that end users can customize. These values should be stored in the item set. One choice is to store each configuration value into its own item in the item set. The drawback of this approach is that multiple items in the item set need to be read or written for a single operation. The code is cumbersome and hard to maintain.

A better choice is to put the configuration values into a single JSON object and store the serialized string of this JSON object. The benefit is that a JSON object is much easier to use than the item set.

Add data type to items

When using APIs to get and set the value of an item, only a string can be used as the data type. To store a boolean value or an integer to the item set, the value needs to be converted to a string. The string value retrieved from the item set also needs to be transformed into the correct data type. For example, a boolean value should be string value true or false before being saved to the item set.

Performing the data type conversion is a common task, so this piece of code can be reused. Listing 6 shows an example implementation.

Listing 6. Add data types to items in the item set
_setupItemSetEditor : function() {
    this._itemSetEditors = {
        "boolean" : {
            fromItemSet: function(rawValue){
                return rawValue === "true" ? true : false;
            },
            toItemSet: function(formattedValue){
                return formattedValue ? "true" : "false";
            }
        },
        "integer" : {
            fromItemSet: function(rawValue){
                return parseInt(rawValue, 10);
            },
            toItemSet: function(formattedValue){
                return formattedValue.toString();
            }
        },
        "json" : {
            fromItemSet: function(rawValue){
                rawValue = dojo.trim(rawValue) != "" ? rawValue : "{}";
                return dojo.fromJson(rawValue);
            },
            toItemSet: function(formattedValue){
                return dojo.toJson(formattedValue);
            }
        },
        "string" : {
            fromItemSet: function(rawValue){
                return rawValue;
            },
            toItemSet: function(formattedValue){
                return formattedValue.toString();
            }
        }
    };
},

_loadItemSet : function() {
    var attrs = this.iContext.getiWidgetAttributes();
    var itemNames = attrs.getAllNames();
    var resultObj = {};
    var itemDescs = this._itemSetDescription;
    var editors = this._itemSetEditors;
    for (var i = 0, n = itemNames.length; i < n; i++) {
        var itemName = itemNames[i],
            itemValue = attrs.getItemValue(itemName),
            itemDesc = itemDescs[itemName],
            dataType = itemDesc.type,
            editor = editors[dataType];
            resultObj[itemName] = editor.fromItemSet(itemValue);
    }
    return resultObj;
},
        
_saveItemSet : function(itemSetObj) {
    var attrs = this.iContext.getiWidgetAttributes();
    var itemDescs = this._itemSetDescription;
    var editors = this._itemSetEditors;
    for (var name in itemSetObj) {
        if (itemSetObj.hasOwnProperty(name)) {
            var value = itemSetObj[name],
                itemDesc = itemDescs[name],
                dataType = itemDesc.type,
                editor = editors[dataType];
            attrs.setItemValue(name, editor.toItemSet(value));
        }
    }
    attrs.save();
},
        
 _describeItemSet : function() {
    this._itemSetDescription = {
        "name" : {type : "string"},
        "age" : {type : "integer"},
        "maritalStatus" : {type : "boolean"},
        "preferences" : {type : "json"}
    };
}

As shown above, the _setupItemSetEditor function is used to create several editors for different data types, including editors for boolean, integer, json, and string. Each editor has two functions, fromItemSet and toItemSet, to convert from and to values in the item set.

The _loadItemSet function loads values from the item set and applies the editors, and then returns a JSON object that contains the items with correct data types. The _saveItemSet function accepts a JSON object and stores its value into the item set.

The editors are also used to get the strings stored. To make the editors work, the iWidget should declare the data types of the items in the item set. Use the _describeItemSet function for the declaration.


Globalization

Using dijits for an iWidget's mode content makes globalization much simpler. The dojo.i18n module provides good support for globalization. A message bundle can be loaded in the iWidget's onLoad() function using dojo.requireLocalization(). Before that, though, the dojo.registerModulePath() function should be invoked to register the module name of the current iWidget. Then Dojo can find the correct path to load the message bundle files. Once the bundle file is loaded, dojo.i18n.getLocalization() is used to get a JSON object that can be used directly in source code to get localized messages. Typically, the JSON object is mixed into dijits and referenced in the templates. Listing 7 shows basic usage of Dojo's globalization support.

Listing 7. Globalization support
dojo.registerModulePath("sampleIWidget", this.rewriteURI("js"));            
dojo.requireLocalization("sampleIWidget", "messages");
this.nls = dojo.i18n.getLocalization("sampleIWidget", "messages");

In the example above, this.rewriteURI() is a utility function to convert a relative path into a full path using this.iContext.io.rewriteURI().


Handling resizing

Some iWidget runtimes allow end users to resize the iWidgets after the iWidgets have been added to the page. An iWidget should react to this user action and re-layout its content accordingly. If the onSizeChanged function is defined in the iWidget's scope object, it will be invoked when the runtime detects that the size of the iWidget has been changed. The iWidget's new size is passed in as a parameter.

The size change can happen when the iWidget is in different modes. When handling the resizing, the iWidget's current mode needs to be checked first. Listing 8 shows the code snippet to handle resizing.

Listing 8. Handle resizing
_setupSizeChangedHandlers : function() {
    this._sizeChangedHandlers = {
        "view" : {
            node : this.byId("viewRoot"),
            callback : dojo.hitch(this, this._viewModeChanged)
        },
        "edit" : {
            node : this.byId("editRoot"),
            callback : dojo.hitch(this, this._editModeChanged)
        }
    };
},
		
_viewModeChanged : function() {
			
},
		
_editModeChanged : function() {
	
},
		
onSizeChanged : function(iEvent) {
    var p = iEvent.payload;
    var width = p.newWidth,
        height = p.newHeight;
    var currentMode = this.iContext.getiDescriptor().getItemValue("mode");
    var handler = this._sizeChangedHandlers[currentMode];
    if (handler && handler.node) {
        var node = handler.node;
        dojo.marginBox(node, {
            w : width,
            h : height
        });
        var callback = handler.callback,
            thisObject = handler.thisObject;
        if (dojo.isFunction(callback)) {
            callback.call(thisObject || dojo.global);
        }
    }
}

In the _setupSizeChangedHandlers function a DOM node to be resized and an optional callback function are registered for each mode. Once the onSizeChanged function is invoked, the DOM node registered for current mode is resized and the callback function is invoked.


Build process

After an iWidget is developed and tested it can be released and deployed to a production environment. Before that, though, a build process should be applied to the iWidget to meet certain non-functional requirements, including compatibility and performance.

Compatibility

A single iWidget is a deliverable to end users, and it may evolve from version to version. Different versions of the same iWidget should coexist on the same page without conflicts. For example, the user might want to use the old version to work with other iWidgets and use the new version to try new features at the same time.

An iWidget should be built with a version number that increases from version to version. To make sure different versions of the same iWidget coexist on the same page, each version should not pollute the global namespace. For example, the sample iWidget declares a global object SampleIWidget. If different versions use the same object name, when they are added to the same page they will share the same object and cause serious problems.

The solution is to add version numbers to an iWidget's global names, including JavaScript object names and CSS class names. For example, the scope object name of the sample iWidget should become SampleIWidget_100 and SampleIWidget_200 for version 1.00 and 2.00 respectively. During the build process, a small script can do full text search and append the version number to those global names.

Package resource files

An iWidget can have many resource files, including JavaScript, CSS, and image files. Resource files are included using the <resource> tag in the iWidget's definition XML file. Each resource file is downloaded by the runtime when loading the iWidget. There is a performance penalty if there are too many HTTP requests to download resources. A good practice is to package multiple files of the same type into a single file. Ideally, there should be only one JavaScript file and one CSS file.

There are many tools you can use to combine and minify JavaScript and CSS files (see Resources). Choose a tool and integrate it into the iWidget's build process. You can also embed all the JavaScript and CSS files in the iWidget's definition XML file. By doing this, only one HTTP request is required to load an iWidget.


Summary

iWidgets are used in many IBM products. With the help of IBM Mashup Center, end users can use different iWidgets to create powerful mashups. The iWidget is a simple component model. It's easy to get started if you want to develop iWidgets. By applying the best practices outlined in this article, you can avoid common mistakes and create high-quality iWidgets.


Download

DescriptionNameSize
Sample iWidget for this article1SampleIWidget.zip7KB

Note

  1. Deploy the sample iWidget to IBM Mashup Center to see how it works.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=514096
ArticleTitle=Create high-quality iWidgets
publish-date=08312010