Developing iWidgets for IBM Mashup Center – a quick-start guide

I’m a great believer in a simple worked example so rather along the lines of my earlier jump-start for SSL and MQTT, I thought I would compose a similar posting for developing iWidgets to use in the IBM Mashup Center. For a deeper dive on iWidgets, have a look at the iWidget Programming Guide, Specification and also the IBM Mashup Center wiki, all of which are there to support the developer. There are several different examples floating around that show various different aspects of iWidgets, I’m going to show a really simple example that reads an ATOM feed and displays the result.

In simple terms, a widget is a user interface component. With particular regard to mashups, the widget is mechanism by which data feeds are visualised. In IBM Mashup Center, when you choose to add a feed to your workbench, you can specify a particular widget to handle the visualisation. iWidget is an emerging standard for defining these user interface components. This is to promote highly reusable and interoperable components that behave in a common way and expose their services in a consistent fashion. This enables the rapid assembly of business applications from components produced by different developers and providers. IBM Mashup Center’s implementation is based on the Dojo Toolkit and the iWidgets come as a package of some JavaScript code and configuration files all wrapped up in a WAR file for deployment.

Thanks to Matt for his help along the way. For those using RSA or RAD 7.x, you may want to check out his guide to creating a development environment.

Creating the iWidget

The iWidget itself is a combination of a Dojo widget class and an XML definition file.

The iWidget XML definition

The XML definition describes the widget configuration with details such as:

  • The modes the iWidget supports. Rather like portlets, iWidgets can have a View mode, an Edit mode and a Help mode, the former being the primary presentation logic, the latter two being the interfaces for the user to modify or gain help about the widget respectively.
  • References to any required resources such as JavaScript files. You should have at least one referenced resource which is the .JS file containing the Dojo widget definition.
  • Markup content for each of the required modes. This is the basic display logic for the iWidget and can of course contain other Dojo widgets within it.
  • The name of the Dojo class implementing the programmatic logic for the widget. This is referred to as the iScope in the XML definition.

My sample iWidget definition is as follows:

<iw:iwidget id="samplewidget"    xmlns:iw="http://www.ibm.com/xmlns/prod/iWidget"    iScope="lm.samplewidget" allowInstanceContent="true"    supportedModes="view edit" mode="view" lang="en"> 

    <iw:resource uri="samplewidget.js" /> 

    <iw:content mode="view">
    <![CDATA[       
         <div id="container">
            Value: <span id="valueNode">0</span>
         </div>           
     ]]>   
    </iw:content>   
    <iw:content mode="edit">       
    <![CDATA[       
       <div style="background-color: white; padding: 2px; border: solid #4078C2 1px;">
            <table border="0">
                <tr>
                    <td colspan="2"><h2>Data feed</h2></td>
                </tr>
                <tr>                   
                    <td>Poll interval (secs):</td><td><input id="pollInterval" size="5"></td>
                </tr>
                <tr>
                    <td colspan="2" height="5"></td>
                </tr>
            </table>           
            <span style="margin-right:12px"><a href="javascript:iContext.iScope().cancel();" id="_IWID_CONF_CANCEL" class="lotusAction" title="Cancel settings">Cancel</a></span>
            <input id="_IWID_CONF_SAVE" title="Save settings" class="lotusFormButton" style="margin-right:10px;" type="button" value="Save" name="save" onclick="iContext.iScope().saveParams();return false;" />
        </div>   
     ]]>
    </iw:content>
</iw:iwidget>

As you can see, the iWidget supports both View and Edit modes. The View simply shows a value, the Edit mode allows us to modify how often the underlying data feed is polled for updates. You will notice from the JavaScript URL link in the Edit markup how we invoke methods on the underlying Dojo widget instance for the iWidget — iContext.iScope()returns the object reference. I am reusing some of the Mashup Center CSS styles for the Save and Cancel triggers so the widget fits into the overall look and feel.

The iWidget Dojo class

In MVC terms you can regard the Dojo class as a controller for the iWidget — for example orchestrating how it responds to events, reads a feed (i.e. the model), manipulates the UI (i.e. the view) and so on. The class implements lifecycle methods for the iWidget such as:

  • onLoad() triggered when the iWidget has finished loading.
  • onview() triggered when the View mode has rendered.
  • onedit() triggered when the Edit mode has rendered.

iWidgets also have a number of name/value pair attributes for storing configuration and data about the widget. These can be anything of your choosing but the one to remember is feedURL since this is set by the Mashup Center when you choose to use your widget to visualise a particular feed. The following snippet is my onLoad() handler and you will see how we extract the content of feedURL and store it as an instance variable.

onLoad: function(){
    this.feedURL = this.iContext.getiWidgetAttributes().getItemValue("feedURL");
    console.info("feedURL = "+this.feedURL);
    this.domID = "_" + this.iContext.widgetId + "_";
    dojo.subscribe ("events/"+this.domID+"/poll", this, "pollForData");
}

There are a few points to note. Firstly, because this is essentially a Dojo widget, we are free to use all the various capabilities of the Dojo Toolkit such as the publish/subscribe framework which I am using here for triggering polls of the ATOM feed. Secondly, you can see how we can interrogate the surroundings of the widget using the iContext instance variable, in this case to generate ourselves a unique identifier to namespace the pub/sub topic and to gain access to the attributes for the instance.

The onview method in my widget is very simple since all I am using it for is as a signal to start polling.

onview: function() {
    if (this.feedURL) {
        console.info("Feed URL is "+this.feedURL);
        this.pollForData();
    }
}

The pollForData() method uses standard Dojo xhrGet to retrieve the ATOM feed. Once processing is complete, the routine simply sets a timeout to poll again.

    pollForData: function() {
        // Request data from the given ATOM feed
        var feedURL = this.feedURL;
        var context = this;
        console.info("Polling for data from "+this.feedURL);
        var bindArgs = {
            url: feedURL,
            error: function(error, ioArg)
                {
                    window.alert("Problem loading feed: "+error);
                }
            ,
            load: dojo.hitch(context, function(xmldata, ioArg){
                if (xmldata != null ) {
                    var xmlDoc = null;
                    if ((xmlDoc = dojox.data.dom.createDocument(xmldata)) === null) {
                        window.alert("No data returned");
                        return;
                    }
                    // Simply reads the text in the content field of an ATOM entry
                    var itemNodes = xmlDoc.getElementsByTagName("content");
                    for (var i = 0; i < itemNodes.length; i++) {
                        var childNodes = itemNodes[i].childNodes;
                        for (var j=0;j<childNodes.length;j++) {
                            // Should only be one <content> node in the ATOM
                            // but if there are more we will take the last one.
                            var val = childNodes[j].nodeValue;
                            this.iContext.getElementById("valueNode").innerHTML = val;
                        }
                    }
                } else {
                    window.alert("No data returned by feed URL.");
                }
                // Now poll again
                this.pollTimeout = window.setTimeout("dojo.publish('events/"+this.domID+"/poll',[]);", this.pollInterval*1000);
            })
        };
        dojo.xhrGet(bindArgs);
    }

onedit populates the form field on the Edit view so that the correct value is presented to the user. In order to activate changes (or indeed undo changes and return to the view) we also need to add two additional methods of our own.

    cancel: function() {
        this.iContext.iEvents.fireEvent("onModeChanged",null,{newMode:'view'});
        return;
    },
    saveParams: function () {
        this.pollInterval = this.iContext.getElementById("pollInterval").value;
        // Restart any polling with the new interval
        if (this.feedURL) {
            if (this.pollTimeout) {
                window.clearTimeout(this.pollTimeout);
            }
        }
        this.iContext.iEvents.fireEvent("onModeChanged",null,{newMode:'view'});
        return;
    },
    onedit: function(){
        this.iContext.getElementById("pollInterval").value = this.pollInterval;
    }

These methods demonstrate a few key features. The first is how to switch modes from within the widget — in both the cancel() and saveParams() methods we wanted return to the View mode so we fired an event into the surrounding context which the Mashup Center will pick up and cause the widget to switch back from Edit. The second point of note is that the iContext provides a getElementById() method of its own — i.e. return the named DOM element within this specific widget’s context rather than the whole HTML page (which may contain other instances of the same widget).

Here is the full listing for the iWidget class.

dojo.provide("lm.samplewidget");
dojo.declare("lm.samplewidget", [], {
    domID: null,
    attributes: ["feedURL"],
    feedURL: null,
    pollInterval: "5",
    pollTimeout: null,

    onLoad: function(){
        this.feedURL = this.iContext.getiWidgetAttributes().getItemValue("feedURL");
        console.info("feedURL = "+this.feedURL);
        this.domID = "_" + this.iContext.widgetId + "_";
        dojo.subscribe ("events/"+this.domID+"/poll", this, "pollForData");
    },

    onview: function() {
        if (this.feedURL) {
            console.info("Feed URL is "+this.feedURL);
            this.pollForData();
        }
    },

    pollForData: function() {
        // Request data from the given ATOM feed
        var feedURL = this.feedURL;
        var context = this;
        console.info("Polling for data from "+this.feedURL);
        var bindArgs = {
            url: feedURL,
            error: function(error, ioArg)
                {
                    window.alert("Problem loading feed: "+error);
                },           
            load: dojo.hitch(context, function(xmldata, ioArg){
                if (xmldata != null ) {
                    var xmlDoc = null;
                    if ((xmlDoc = dojox.data.dom.createDocument(xmldata)) === null) {
                        window.alert("No data returned");
                        return;
                    }
                    // Simply reads the text in the content field of an ATOM entry
                    var itemNodes = xmlDoc.getElementsByTagName("content");
                    for (var i = 0; i < itemNodes.length; i++) {
                        var childNodes = itemNodes[i].childNodes;
                        for (var j=0;j<childNodes.length;j++) {
                            // Should only be one <content> node in the ATOM
                            // but if there are more we will take the last one.
                            var val = childNodes[j].nodeValue;
                            this.iContext.getElementById("valueNode").innerHTML = val;
                        }
                    }
                } else {
                    window.alert("No data returned by feed URL.");
                }
                // Now poll again
                this.pollTimeout = window.setTimeout("dojo.publish('events/"+this.domID+"/poll',[]);", this.pollInterval*1000);
            })
        };
        dojo.xhrGet(bindArgs);
    },
    cancel: function() {
        this.iContext.iEvents.fireEvent("onModeChanged",null,{newMode:'view'});
        return;
    },
    saveParams: function () {
        this.pollInterval = this.iContext.getElementById("pollInterval").value;
        // Restart any polling with the new interval
        if (this.feedURL) {
            if (this.pollTimeout) {
                window.clearTimeout(this.pollTimeout);
            }
        }
        this.iContext.iEvents.fireEvent("onModeChanged",null,{newMode:'view'});
        return;
    },
    onedit: function(){
        this.iContext.getElementById("pollInterval").value = this.pollInterval;
    }
 });

Here is a sample of the ATOM feed that is consumed by the sample.

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Example pressure reading Feed</title>
  <link href="http://example.org/"/>
  <updated>2003-12-13T18:30:02Z</updated>
  <author>
    <name>Martin</name>
  </author>
  <id>urn:uuid:62a76c80-d399-11d9-b93C-0003939e0af6</id>
  <entry>
    <title>Pipeline pressure reading</title>
    <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
    <updated>2009-01-30T18:30:02Z</updated>
    <summary>Pressure reading (in PSI)</summary>
    <content>23</content>
  </entry>
</feed>

Packaging and deploying the iWidget

I have assembled my iWidget in a WAR file structure with the XML definition and JavaScript in the root.

image

Two further files are required in the WEB-INF folder, mashup.properties and catalog.xml. The former contains lower-level information about the widget such as a unique identifier and the name of the context root, the latter contains more descriptive information about the iWidget.

Here is my mashup.properties file.

contextRoot=SampleWidget
componentName=samplewidget

And here is the catalog.xml file.

<catalog>
    <category name="Demo">
          <title>
          <nls-string lang="en">Demo</nls-string>
          </title>
          <description>
          <nls-string lang="en">Demo Widgets</nls-string>
          </description>
        <entry id="SampleWidget" unique-name="SampleWidget">
            <title>
                <nls-string lang="en">Sample Widget</nls-string>
            </title>
            <description>
               <nls-string lang="en">Shows a value read from an ATOM feed.</nls-string>
            </description>
            <definition>SampleWidget.xml</definition>
            <content>http://www.ibm.com</content>
            <preview>http://www.ibm.com</preview>
            <icon>images/icon.png</icon>
        </entry>
    </category>   
</catalog>

With all these files in place, we simply export the structure (I am using RAD 7.0) to a WAR file and we can now install it into the IBM Mashup Hub for people to use. One thing to note, if you specify an icon in the XML, ensure this is exported as part of your WAR otherwise the WAR will not install correctly into the Mashup Hub.

Using the iWidget

The final step is to try out the iWidget. We shall do this by adding a feed to the Mashup Hub and then in turn to Lotus Mashups specifying our widget as the viewer. As I’ve done previously, I’ve made a short video to show how it is done.

Advertisements

10 responses to “Developing iWidgets for IBM Mashup Center – a quick-start guide

  1. Just passing by.Btw, your website have great content!

    _________________________________
    Making Money $150 An Hour

  2. Pingback: Persisting custom attributes for an iWidget in IBM Mashup Center « Martin Gale’s blog

  3. Can you use jQuery inside an IBM iWidget?

    I cannot get ANY jQuery plug-ins to work!

    Seems that everything is going through some enabler.js ??

    Anyone have a clue what I’m talking about?

    • Hi Barry,

      I have not personally used jQuery to implement an iWidget, you may want to monitor the IBM Mashups Wiki, though I suspect the examples will be based on the Dojo Toolkit -> http://www-10.lotus.com/ldd/mashupswiki.nsf

    • Hey Barry, I used jQuery in an IBM iWidget. The problem was that the only way I could get it to work was pasting the entire jQuery library into my widget JavaScript.

      One alternative would be to dynamically include the jQuery JavaScript file from your widget JavaScript file – but that is not supported by all browser (it didn’t work in IE).

  4. Just found this… great job!! Thanks for documenting this!!

    • Hi Luis,

      Glad you found it of use, the approach I take is to think about what would help me if I were trying to build a similar solution.

      Thanks,
      Martin

  5. Hi

    I am getting this error when i try to drag the installed iwidget to my page which is
    unable to load error response code 404.

    Can you please help me with this?

    • Hi Mayuran,

      Apologies, that might be down to a problem in the catalog XML — I’ve just fixed it above (thanks to Daria for finding it!). The .xml extension was missing from the definition tag.

      I hope that fixes the issue.

      Thanks
      Martin

      • Hi Martin

        Thanks for the reply. The above one works but i have a problem where i get an error in the firebug as this.icontext has no properties..

        Any idea what this mean?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s