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.
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.
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.
- Add an iWidget package in WAR or zip format.
See Resources for links to get the tools mentioned above.
Once the development environment is ready, you can start to create new iWidgets. Use the following steps:
- Create a Dynamic Web Project in Eclipse for an iWidget.
- Create the iWidget's definition XML file that contains minimum content only.
- Deploy the project to Apache Tomcat using Eclipse WTP, and start the server.
- Get the URL of the iWidget's definition XML and add it to IBM Mashup Center.
- View the iWidget in IBM Mashup Center.
- 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
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}.
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 usedojoAttachPointto 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 usedojoAttachEventto bind event listeners declaratively. - Add globalization support by using
Dojo's
dojo.i18nmodule (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.
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.
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.
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.
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.
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().
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.
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.
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.
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.
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.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample iWidget for this article1 | SampleIWidget.zip | 7KB | HTTP |
Information about download methods
Note
- Deploy the sample iWidget to IBM Mashup Center to see how it works.
Learn
- Read iWidget Specification 2.0 to
understand iWidget's component model.
- Follow the IBM Mashup Center
wiki for community-driven, authoritative, and current content.
- Read the Dijit Reference Guide to
understand dijit.
- The Dojo Internationalization
Reference Guide has information on internationalization (the process of making an application flexible to work in different
languages and respect different conventions and customs).
- Stay current with developerWorks technical events
and webcasts focused on a variety of IBM products and IT industry
topics.
- Attend a free developerWorks Live!
briefing to get up to date quickly on IBM products and tools as
well as IT industry trends.
- Follow developerWorks on
Twitter.
- Watch developerWorks on-demand demos
ranging from product installation and setup demos for beginners, to
advanced functionality for experienced developers.
Get products and technologies
- Try IBM Mashup Center.
- Download Eclipse and Web Tools Platform.
- Download Apache
Tomcat.
- Use Dojo ShrinkSafe and YUI Compressor
to combine and minify JavaScript files.
Discuss
- Create your My developerWorks profile today and set up a watchlist on widgets or Dojo.
Get connected and stay connected with My developerWorks.
- Find other developerWorks members interested in web development.
- Web developers, share your experience and knowledge in the Web development group.
- Share what you know: Join one of our developerWorks groups focused on web topics.
- Roland Barcia talks about Web 2.0 and middleware in his blog.
- Follow developerWorks' members' shared bookmarks on web topics.
- Get answers quickly: Visit the Web 2.0 Apps forum.
- Get answers quickly: Visit the Ajax forum.




