IBM WebSphere sMash is a flexible and agile platform for developing and delivering Web 2.0 applications. With an easy to use programming model that includes dynamic languages, WebSphere sMash provides an excellent environment for building RESTful services to extend an SOA, enabling you to consume services in the way that makes the most sense for your situational objectives. This article describes how to build a Google Gadget, a lightweight module constructed out of HTML, JavaScript™ and XML, that can be placed in a multitude of Web pages that you can power with WebSphere sMash.
Gadgets are simple HTML and JavaScript mini-applications that provide a lightweight way for you to share your data with others. Written once, a gadget can be utilized in many places, including iGoogle, Google Maps, Orkut, IBM WebSphere Portal, or in virtually any Web page or desktop. Figure 1 shows the iGoogle Web page, a customizeable, portal version of the Google home page, populated with different Google Gadgets pulled from other sites such as Slashdot.org, Fool.com, BBC News, and Kiplinger Personal Finance.
Figure 1. iGoogle Web page
A basic gadget can be constructed from HTML, XML and JavaScript. A gadget uses the XML document to describe the HTML and JavaScript that will be rendered on the gadget container, which is the set of functions that enables a gadget to be displayed in the page. This article uses the iGoogle.com page as the container, but you could use a product like WebSphere Portal or snippets of code provided by Google to display the gadget on a typical Web page. The place to start building a gadget is in the gadget specification file, an example of which is shown in Listing 1.
Listing 1. Gadget specification for a Google Gadget
<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="Gadget Sample"
title_url="http://www.ibm.com/developerworks/"
height="200"
author="Jay Palat"
author_email="vpalat@us.ibm.com"/>
<UserPref name="exampleStr" display_name="Name" required="true" />
<Content type="html">
<![CDATA[
Hello, world!]]>
</Content>
</Module> |
When you break it down, you can see that a gadget is a module that consists of three parts:
-
Module preferences
ModulePrefs (Listing 2) refer to the module or gadget itself, providing the attributes for publication and subscription. The display attributes include the title, title URL, gadget dimensions (height and width), scrolling (if the gadget content exceeds the page limits), and author information (name, e-mail address, and so on). If a module is going to use a special library, then this must also be included in the ModulePrefs.
Listing 2. Module preferences<ModulePrefs title="Gadget Sample" title_url="http://www.ibm.com/developerworks/" height="200" author="Jay Palat" author_email="vpalat@us.ibm.com"/>
Gadgets also support other features (not detailed in this article), including locale support and compatibility requirements. Locale enables gadgets to have specific content based on language and country, and compatibility enables gadget authors to set rules requiring the gadget environment to be running under certain conditions. For instance, a gadget might require a specific platform, browser, or plug-in in order to run.
-
User preferences
UserPref (Listing 3) enables you to set specific preferences for the gadget. For example. you might want users to be able to change the appearance of the gadget. A user preference has several components. The name attribute is the name of preference itself; unless the display_name is set, the user will see the UserPref name value by default. UserPref supports multiple data types, including string (the default), bool, enum, hidden and list.
Listing 3. User preferences<UserPref name="exampleStr" display_name="Name" required="true" /> <UserPref name="exampleStr2" display_name="Photo" default_value="http://doc.examples.googlepages.com/Rowan-headshot.jpg"/> <UserPref name="exampleBool" display_name="Show Photo?" datatype="bool" default_value="true"/> <UserPref name="exampleEnum" display_name="Color" default_value="Yellow" datatype="enum" > <EnumValue value="Red" /> <EnumValue value="Yellow" /> <EnumValue value="Blue" /> </UserPref>
Users cannot view or edit hidden strings. If a required flag is set to true, then the gadget will not render until the required UserPref has been set.
-
Content
Content is the substance of your gadget. A gadget must declare the type of content available (the options are HTML and URL). Gadgets of type HTML encapsulate the HTML and JavaScript necessary to make the gadget work in the XML document itself. The code is all self-contained within the gadget. For this reason, the CDATA tag is required for gadgets of content type HTML, as in:
<Content type="html">
<![CDATA[ HTML here... ]]>Notice below that the HTML included with the gadget does not contain the <html>, <head>, or <body> tags; these items are handled at the gadget container level and should not be included in the gadget's HTML fragment:
<Content type="html">
<![CDATA[
Hello, world!]]>
</Content>As with typical HTML pages, JavaScript can be included inline or via an external file. Google provides a set of JavaScript libraries to help out. The Core JavaScript libraries provide access to the user preferences that were created in UserPrefs section.
Listing 4. Using Core JavaScript library<![CDATA[ <script type="text/javascript"> function getPrefs() var prefs = new _IG_Prefs(__MODULE_ID__); var name = prefs.getString("exampleStr"); var url = prefs.getBool("exampleStr2"); var bool = prefs.getBool("exampleBool"); var primaryColor = prefs.getString("exampleEnum"); ...
Google also provides libraries for dealing with content, including feeds, images, and XML documents. Table 1 shows some examples from the Google Gadgets API Reference page.
Table 1. Select features available from the Google Gadgets Core JavaScript library
| Function | Description |
|---|---|
_IG_FetchContent(url, func, {refreshInterval:num})
| Fetches the content at url. When it's ready, calls func. Note that _IG_FetchContent() is asynchronous, meaning that it returns immediately and then calls the inner function later, when the fetch finishes. This means that you must put any dependent code inside the callback function, or inside functions that are called by the callback function. The optional refreshInterval parameter lets you override the default caching behavior by refreshing the cache every num seconds. |
_IG_FetchXmlContent(url, func, {refreshInterval:num})
| Fetches the XML content at url. When it's ready, calls func. The optional refreshInterval parameter lets you override the default caching behavior by refreshing the cache every num seconds. Also asynchronous. |
_IG_FetchFeedAsJSON(url, func, num_entries, get_summaries)
| Fetches the RSS / Atom feed content at url and returns it as a JSON object. When it's ready, calls func. Fetches the number of feed entries specified by num_entries (default is 3, possible range is 1-100), and optionally fetches entry summaries depending on the value of get_summaries (default is false). Also asynchronous. |
_IG_RegisterOnloadHandler(func)
| Event handler that gets called on load. Takes a single argument, which is a function that is called after the gadget loads. The passed function takes no arguments. |
_gel(id)
| A wrapper around the JavaScript document.getElementById() function. |
_args()
| Returns the URL parameter
key-value pairs from document.location as an associative array. So, for example, &foo=bar&up_cats=meow
would be returned as {"foo":"bar", "up_cats":"meow"}. Here is an example of how you might use it to fetch the language or country: var lang = _args()["lang"];
|
_IG_GetImage(url, {refreshInterval:num})
| Returns the cached version of the image specified by url. This can improve performance in gadgets that have static images. For example:
var img = _IG_GetImage(my_image_url);
The optional refreshInterval parameter lets you override the default caching behavior by refreshing the cache every num seconds. The functions _IG_GetImageUrl() and _IG_GetCachedUrl() also give you access to cached content. (Also see Improving Gadget Performance.) |
_IG_GetCachedUrl(url, {refreshInterval:num})
| Returns the proxy URL for the cached version of the file specified by url. Using cached content can improve gadget performance. The optional refreshInterval parameter lets you override the default caching behavior by refreshing the cache every num seconds. |
While many gadgets are produced simply using the HTML type, there are circumstances where it makes more sense to run the gadget from a server-based solution. For those cases, you can use the content type URL:
<Content type="url"
href="http://www/cgi-bin/example/gadgets/mystats.cgi" />
URL-based gadgets do all their processing and logic on the server side. They are hosted via an iframe. As the gadget creator, you are responsible for making sure the iframe edges are hidden using CSS.
Building and publishing a gadget
Although you can use any simple text editor to create the basic XML specification, this article uses the Google Gadgets Editor (Figure 2) for ease of use. The Google Gadgets Editor is a basic text editor with syntax highlighting that also provides a preview mode to show how the gadget will look once it is deployed (Figure 3). The Google Gadgets Editor page provides samples of other available gadgets, as well as API examples.
Figure 2. Google Gadgets Editor: Edit mode
Figure 3. Google Gadgets Editor: Preview mode
(The preview function will not be used in this example, so set the value to false when working in the editor, but true when publishing.)
The Google Gadgets Editor offers the following commands:
- New: Create a new file
- Open...: Open an existing file from the Google hosting platform
- Open from URL: Open a file from a remote URL
- Save: Save the file
- Save As: Save with a new name
- Rename: Rename an existing file
- Upload: Upload a file to edit
- Publish: Publish the gadget
Publishing a gadget places the content in publicly available space. Publishing to Google enables other developers to take advantage of the Google infrastructure in their development, including caching and high availability of services. That said, you can publish your gadget to any Web site and simply point your gadget container page to the URL of your gadget.
In the next sections, you will see how to create content based on feeds using WebSphere sMash's flow models and feed tools, and then how to power the gadget using WebSphere sMash.
WebSphere sMash provides the ability to aggregate feeds and coordinate services together using an Assemble component. The feed aggregation feature is used here to take data from existing feeds and power a gadget that will consume the data.
A flow is composed of the following components and functions:
- Feed: A source feed. WebSphere sMash can consume feeds of the RSS 0.x, RSS 1.0, RSS 2.0, Atom 0.3, and Atom1.0 types.
- SortFeed: Sorts feeds. Using SortFeed, feed entries can be sorted in ascending or decending order by attributes such as title or date.
- AggregateFeeds: Supports two or more input feeds and outputs a single output feed of the Atom 1.0 type.
- FilterFeed: Enables the flow creator to give the flow selection criteria. You can use keyword or a boolean XPath 1.0 expression to select entries for the output Atom node.
- Unique: Removes duplicate entries by using an XPath expression to identify the duplicated facet.
- Truncate: Returns a specific set of entries, such as the first ten entries of the input feed.
Certainly, there are more, but these are sufficient for our purposes. (See the WebSphere sMash documentation for other available operators.)
Let's build a gadget that uses a WebSphere sMash flow to aggregate a pair of feeds from IBM developerWorks to WebSphere sMash:
-
If you don’t have Project Zero or WebSphere sMash installed already, you can install WebSphere sMash Developer’s Edition for free from the Project Zero Web site, which also provides install instructions and documentation.
-
Once installed, you can use the WebSphere sMash App Builder (Figure 4) to start building your flow. Start by entering the appropriate launch commands from a command prompt. (The commands in Listing 5 were used to launch the App Buiilder on Linux. To find the commands for your platform, refer to the startAppBuilder script in the directory where you installed the WebSphere sMash CLI module.)
Listing 5. Launch WebSphere sMash App Buildervpalat@vpalat-laptop2:~/zero$ ./startAppBuilder Application started and servicing requests at http://localhost:8070/ CWPZT0600I: Command start was successful vpalat@vpalat-laptop2:~/zero$
Figure 4. WebSphere sMash App Builder
-
To build your feed, you will create a new application by clicking on the New Application link in the left navigation box (Figure 4).
-
In the New Application panel (Figure 5), describe your application by entering the following data:
- Application Name:
GadgetFeed - Application Path:
<HomePath>/GadgetFeed/ - Application Description:
Feed Aggregation for Google Gadgets
Click the Create button to return to the updated main menu (Figure 6).
Figure 5. New Application panel
- Application Name:
-
To edit your application, clicking on the pencil icon (Figure 6). This will display the GadgetFeed Editor (Figure 7), from which you can add and edit the files for your application.
Figure 6. Main menu with Edit icon
Figure 7. GadgetFeed Editor
-
To create a new flow, you need to create a new file in the public folder of your application directory by clicking on the New File => New Page (in /public) (Figure 8).
Figure 8. New Page menu
-
When prompted for the file you want to create, enter
/public/gadget/websphere.flow, to be created in the public directory (Figure 9).
Figure 9. File Create
-
App Builder then prompts you to add the zero.assemble.flow dependency (Figure 10). Click Add.
Figure 10. Add dependency
Why add the dependency? Unlike an application server, which provides all the class libraries up front, WebSphere sMash adds the dependencies as they are needed so that your application will never have more than exactly what it needs. If you look at your dependency list in Figure 11, you'll see that it only shows the Modulegroups you are using: zero.core, zero.assemble.flow, and zero.flow.management.
Figure 11. Application Dependencies
-
In the activities drop down menu, make sure the selection is set to Feed Activities.
Figure 12. Updating the activities
-
Import feeds for the following URLs by dragging the Feed icon onto the Editor panel and update the URL values with these (Figure 13):
http://www.ibm.com/developerworks/views/webservices/rss/libraryview.jsp
http://www.ibm.com/developerworks/views/websphere/rss/libraryview.jsp
Figure 13. Adding the feed operators
-
Merge these feeds together using the FeedAgregator. Drag an aggregateFeeds operator to the editor (Figure 14). Change the title and description to suit, then pull the output of the feeds to the input of the aggregateFeeds widget.
Figure 14. Adding the aggregateFeeds operator
-
To aggregate the feeds, drag the output of the feeds to the input of aggregateFeeds (Figure 15).
Figure 15. Aggregating the feeds
-
The aggregateFeeds widget will return an Atom object, but in order to get it back to the requester, you need to include a replyGet entry. A replyGET activity provides the response to an HTTP GET request previously matched to a receive activity. Each flow can only execute to one HTTP response. Drag a replyGet item from the Feed Activities to the Editor. Link it to the aggregateFeeds by taking the output of the aggregateFeeds and dragging it to the replyGet item (Figure 16).
Figure 16. Adding the replyGet
-
The editor will automatically save changes for you. Names of unsaved files are annotated with an asterisk (*) (Figure 17), as opposed to names of saved files, which are not (Figure 18).
Figure 17. Unsaved flow
Figure 18. Saved flow
-
To start your flow, click Run in the upper left corner of the panel (Figure 19). This will start the application running on port 8080.
Figure 19. Flow is stopped
Figure 20. Flow is running
-
Invoke your flow by navigating to
http://localhost:8080/gadget/websphere.flowin a browser (Figure 21).
Figure 21. Invoking your feed
-
This looks good, but why does "Integrating IT monitoring and business activity monitoring" show up in the list twice? It turns out that there are a few articles that are shared between the two feeds that you you brought together in this flow. You can fix this by adding the unique operator. Drag the unique operator from the Feed Activities menu to the editor (Figure 22).
Figure 22. Adding a unique operator
-
Remove the link between aggregateFeeds and replyGET by clicking on the link and then selecting the scissors icon to delete the link (Figure 23).
Figure 23. Removing the link
-
Once you've deleted the link, you can connect the aggregateFeeds to unique_0, and unique_0 to replyGET_0. Make the article unique by title. To do this, select atom:title in the unique_0 drop down (Figure 24), save the flow.
Figure 24. Choosing the evaluation
-
Retry
http://localhost:8080/gadget/websphere.flow(Figure 25). You will see that "Integrating IT monitoring and business activity monitoring" now displays only once.
Figure 25. Modified feed of unique entries
Now that you have a feed, let's turn it into a gadget:
-
Start with a basic example with no user preferences, but using some JavaScript to get started (Listing 6).
Listing 6. A simple start<?xml version="1.0" encoding="UTF-8" ?> <Module> <ModulePrefs title="Gadget Sample" title_url="http://www.ibm.com/developerworks/" height="200" author="Jay Palat" author_email="vpalat@us.ibm.com"/> <Content type="html"> <![CDATA[ <style> #content_div { font-size: 80%; margin: 5px; background-color: #FFFFBF;} </style> <div id=content_div></div> <script type="text/javascript"> function showfeed__MODULE_ID__() { var html = "Powered by JavaScript"; _gel("content_div").innerHTML = html; }; _IG_RegisterOnloadHandler(showfeed__MODULE_ID__()); </script> ]]> </Content> </Module>
In this example, you start with a stylesheet to provide the formatting for your content. You create a div container (<div id=content_div></div>) in which to place the content, and you finally have your JavaScript function (Listing 7).
Listing 7. JavaScript functions<script type="text/javascript"> function showfeed__MODULE_ID__() { var html = "Powered by JavaScript"; _gel("content_div").innerHTML = html; }; _IG_RegisterOnloadHandler(showfeed__MODULE_ID__()); </script>
The function is showfeed__MODULE_ID__(), with __MODULE_ID__ to be replaced by the identifier for this particular module. The function itself creates a simple HTML string and places it within the content div you created a few lines earlier. The last line _IG_RegisterOnloadHandler(showfeed__MODULE_ID__()); will run showfeed__MODULE_ID__() on startup. Figure 26 shows what your sample looks like in the Google Gadgets Editor Preview mode.
Figure 26. Preview mode
-
This is a good start to using JavaScript in your gadget, but let's see how you can incorporate your feed. For this, you will use the
_IG_FetchFeedAsJSONcommand, which takes these attributes:- URL: the location of your feed (string)
- function: function that the gadget engine will use once it has retrieved the feed (JavaScript function)
- number of entries: number of entries: number of entries to be retrieved from the feed (int, default 3)
- get summaries: if true, then the function will get the summaries (Boolean, default is false)
So let's incorporate it. Be aware that up until this point, you have only been looking at WebSphere sMash from the localhost. From this point on, your WebSphere sMash instance needs to be publicly available, since the Google infrastructure needs to be able to see the public feed.
Listing 8. New script for displaying feed content<script type="text/javascript"> var entries = 5; var summaries = false; _IG_FetchFeedAsJSON("http://6myhost.com:8080/gadget/websphere.flow", function (feed) { if (feed == null){ alert("There is no data."); return; } var html = "" html += "<div><b>" + feed.Title + "</b></div>"; html += "<div>" + feed.Description + "</div><br>"; if (feed.Entry) { for (var i = 0; i < feed.Entry.length; i++) { html += "<div>" + "<li><a target='_blank' href='" + feed.Entry[i].Link + "'>" + feed.Entry[i].Title + "</a> "; } html += "</div>"; } _gel("content_div").innerHTML = html; }, entries, summaries); </script>
In Listing 8, the _IG_FetchFeedAsJSON function was added, and your sMash instance was added for the feed. This should be as easy as using your Internet accessible IP address for localhost. The function (feed) function is outlined as part of IG__FetchFeedAsJSON. The inline function checks to make sure that you have a feed (if the feed is empty, it returns an alert) before populating the HTML with the details of the feed. The number of entries is set to 5 and set the summaries are set to false. Applying this to the example, a preview is shown in Figure 27.
Figure 27. Preview incorporating feed
-
This is good, but you're only seeing three of your five feed items. To fix this, update the ModulePrefs to permit scrolling. You can do this by adding the scrolling parameter to your gadget code, shown in bold type below.
Listing 9. Revised gadget<ModulePrefs title="Gadget Sample" title_url="http://www.ibm.com/developerworks/" height="200" author="Jay Palat" scrolling="true" author_email="vpalat@us.ibm.com"/>
This modification gives you the result in Figure 28.
Figure 28. Scrolling gadget
-
This is good, but you can do better. What if users want more than five entries or want to see the summaries for these articles? You can handle these choices by using user preferences. Add a user preference for number of entries and another for the use of summaries (Listing 10).
Listing 10. Add user preferences<UserPref name="num_entries" display_name="Number of Entries?" default_value="5"/> <UserPref name="show_summaries" display_name="Show summaries?" datatype="bool" default_value="false"/>
With these <UserPref> entries in the XML, next get the JavaScript to extract them. Use the Google Gadget JavaScript Core Library to get the preferences.
Listing 11. Extract user preferencesvar prefs = new _IG_Prefs(__MODULE_ID__); var summaries = prefs.getBool("show_summaries"); var entries = prefs.getInt("num_entries");
This replaces your previous declarations of summaries and entries. You can now incorporate this into your gadget logic. The completed gadget with the new logic for handling the summaries appears in bold in Listing 12.
Listing 12. Complete gadget code<?xml version="1.0" encoding="UTF-8" ?> <Module> <ModulePrefs title="Gadget Sample" title_url="http://www.ibm.com/developerworks/" height="200" author="Jay Palat" scrolling="true" author_email="vpalat@us.ibm.com"/> <UserPref name="num_entries" display_name="Number of Entries?" default_value="3"/> <UserPref name="show_summaries" display_name="Show summaries?" datatype="bool" default_value="false"/> <Content type="html"> <![CDATA[ <style> #content_div { font-size: 80%; margin: 5px; background-color: #FFFFBF;} </style> <div id=content_div></div> <script type="text/javascript"> var entries = 5; var summaries = false; var prefs = new _IG_Prefs(__MODULE_ID__); var summaries = prefs.getBool("show_summaries"); var entries = prefs.getInt("num_entries"); _IG_FetchFeedAsJSON("http://66.93.60.86:8080/gadget/websphere.flow", function (feed) { if (feed == null){ alert("There is no data."); return; } var html = "" html += "<div><b>" + feed.Title + "</b></div>"; html += "<div>" + feed.Description + "</div><br>"; if (feed.Entry) { for (var i = 0; i < feed.Entry.length; i++) { html += "<div>" + "<li><a target='_blank' href='" + feed.Entry[i].Link + "'>" + feed.Entry[i].Title + "</a> "; if (summaries==true) { html += "<br><i>" + feed.Entry[i].Summary + "</i>"; } } html += "</div>"; } _gel("content_div").innerHTML = html; }, entries, summaries); </script> ]]> </Content> </Module>
-
To be useful, a gadget needs to be published. Luckily, the Google Gadgets Editor makes publication easy. From the editor menu, simply select File => Publish.
Figure 29. Publish gadget
The editor performs a validation check on (Figure 30). Click OK and your gadget will be published (Figure 31).
Figure 30. Gadget validation
Figure 31. Gadget published
-
While you can publish to the iGoogle directory for everyone to see your gadget, it might make more sense to publish your gadget to your iGoogle page while you continue to work on it. Select Add to my iGoogle page (Figure 31) and you will receive a confirmation message similar to that shown in Figure 32. Your gadget is now on iGoogle.
Figure 32. Add to iGoogle confirmation
With these steps completed, your gadget is now available on your iGoogle page, as shown in Figure 33.
Figure 33. iGoogle Web page with sample gadget
This article examined how to create a gadget, how to use the different parts of the gadget XML specification, and how you can create a feed assembly in WebSphere sMash using the AppBuilder GUI. The gadget and the feed assembly were then combined, using a feed generated by WebSphere sMash to power the gadget. By applying these instructions to your own situational application requirements, you can build a gadget from scratch and deploy it to your iGoogle home page.
The author thanks Catherine Rivi, Karl Bishop, and Mary Ann Johnson for their help in reviewing and commenting on this article.
Learn
-
IBM WebSphere sMash
documentation
-
Feed operators reference
-
Getting started with Google Gadgets
-
Google Gadgets guidelines
-
IBM developerWorks
WebSphere sMash zone
Get products and technologies
Discuss

Vijaykumar Palat is a Consulting IT Specialist for IBM as a member of IBM Software Services WebSphere for IBM, servicing the IBM Global Account. Jay has been a lead architect and developer for IBM internal projects, as well as for external clients and business partners, developing solutions for WebSphere Application Server and WebSphere Portal. He has an active interest in Web 2.0 technolgies, open source, and virtualization. He has a Masters of Information Technology from Carnegie Mellon University and BS in CS from University of Pittsburgh.
Comments (Undergoing maintenance)





