This tutorial is for those of you who are familiar with the concept of mobile widgets but as yet have not developed any of your own. We shall quickly run through the basics of widget development, and at the end you will have all you need to go ahead and develop your own useful widgets. I hope to address here both the complete newcomer and those with prior Web development experience looking to branch out into mobile widgets. There are five sections to the tutorial and key components of widget development are addressed in each. In following each section you will have constructed a collection of widgets designed to introduce you to specific functionalities which you may find useful when creating your own widgets for the S60 widget platform.
To get started with widget development the basic requirements are a text editor and a browser. In addition to this if you have a flair for design and want to be a bit more creative then an image editing application is useful for creating images to give your widget your own look and feel. Photoshop is of course excellent and the industry standard, if a little pricey. GIMP is a great free alternative.
Since widgets are generally constructed from Web development standards such as HTML, CSS and JavaScript, the browser is a simpe way to test the widget on a PC before deploying to a mobile device. The best browsers are the ones that stick rigorously to the W3C standards (although these are unfortunately not necessarily the most popular). Firefox, Opera and Safari are good examples of such browsers.
Firefox has the added benefit of the excellent Firebug plug-in which provides a rich set of tools to help the developer diagnose any issues with the code. Firebug does this by providing an infrormation window which allows the developer to check the layout of elements, inspect the DOM, debug JavaScript, and view network activity - useful for those AJAX calls which we'll come on to later. However, as of version 3.1 (March 2008) Apple's Safari now has a similar tool called Web Inspector which does a similar job.
HTML markup and JavaScript are essential for the design and implementation of every widget, therefore I am going to assume a fairly competent knowledge of (X)HTML, CSS and JavaScript, head over to the useful W3Schools if you need to brush up on any of these.
In addition, knowing your way around the Document Object Model (DOM) will prove useful as this is also asubstantial technology for manipulating the layout of a widget. It is worth noting that the Nokia WRT supports the following Internet technology standards:
Once your widget development is at a stage when you've tested your widget in a browser and everything works fine then you'll want to deploy and test your widget on a mobile device. So for this you'll need a Series 60 device with the Web Run-Time installed.
The S60 WRT is standard on S60 3rd Edition Feature Pack 2 phones, and has also been back ported to some 3rd Edition FP 1 devices. At the time of writing commercially available WRT enabled devices include the Samsung SGH-i450, Samsung SGH-i560 and the latest versions of Nokia's N95. The WRT is part of the N95 firmware v21.* and N95 8GB firmware v15.* and later.
Alternatively you could download and install the S60 SDK. The device emulator in the S60 3rd Edition, FP2 C++ SDK supports the WRT and networking functionality for developing and testing widgets directly in the emulator, although this will require a fair amount of setup.
Ok, here we go with a simple 'my first widget' to start off with.
Construction begins with creating a folder to hold the constituent parts of the widget. Our 'Hello Widget' widget contains the following items...
Hello Widget/
Info.plist
index.html
Layout.css
Icon.png
Resources/
images/
background.png
widget_bg.png
Note that the only mandatory components of a widget are shown in bold, these are the Info.plist manifest file and a HTML file. The mandatory components must be placed in the root of this folder, other files can be placed either in the root folder or within subfolders.
In our root folder we also have a few other important items; a CSS style sheet which we have referenced from the index.html file, an icon to be displayed in the S60 Applications folder when the widget is installed, and finally a folder of image files. Note that the icon must be a in the PNG image format and that 88x88 pixels is the recommended size - although in my experience this size of icon is poorly resized when displayed on a QVGA (240x320 pixel) display device.
The manifest file is in XML format and contains the property and configuration information of the widget as shown below.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Nokia//DTD PLIST 1.0//EN" "http://www.nokia.com/NOKIA_COM_1/DTDs/plist-1.0.dtd"> <plist version="1.0"> <dict> <key>DisplayName</key> <string>Hello Widget</string> <key>Identifier</key> <string>com.example.widget.hellowidget</string> <key>Version</key> <string>1.0.0</string> <key>MainHTML</key> <string>index.html</string> <key>AllowNetworkAccess</key> <true/> </dict> </plist>
The DisplayName String specifies the name of the widget that is displayed on the widget installation confirmation dialog and shown next to the widget's icon in the 'Installed' folder of the application menu. The Identifier String specifies a unique identifier for the widget in reverse domain format e.g., com.example.widget.hellowidget. And finally the MainHTML String specifies the name of the widget's main HTML file. There is only one HTML page per widget and the name of the HTML file must be predefined within the Info.plist properties. This file is a standard HTML file and should look something like the following...
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <link rel="stylesheet" type="text/css" href="Layout.css" /> <!-- link to our css layout styles --> <title>Hello Widget</title> </head> <body> <div id="Widget"> <!-- the widget panel element --> <div id="TextPanel">Hello Widget!</div> <!-- text panel element --> </div> </body> </html>
In order to test the widget drag the main HTML file into a browser window, there you can use tools such as Firebug to check the layout of all the elements to make sure everything is displayed as you intended.
In order to deploy the widget to a device you need to create a widget package file. To do this simply zip up the 'Hello Widget' folder and change the file extension to *.wgz instead of *.zip.
You can transfer the file to the device either via a Bluetooth connection (the package appears in the messaging Inbox folder), or via memory card. Then select the file to start the installation process. If the phone is unable to install the file (the file type is unrecognised) then it is likely that the WRT isn't supported on that device.
Alternatively a widget can be installed by pointing the S60 browser to a .wgz file hosted on a server online, as long as the server has been set up to deliver .wgz files with the correct MIME type.
It is of course easy to examine the contents of a widget bundle, and this is a great way to learn from other developers, simply change the *.wgz file back to *.zip and unpack as a normal zip archive.
Now we've developed the basics its time to introduce some user interaction. This is where we start to utilise the S60 WRT API see here and here for more detailed information about the API.
The main difference between this and the previous widget is the introduction of JavaScript files which will give us our interactivity. Several JavaScript calls are made to the S60 widget object API and to an embedded object in the HTML which gives us access to system information about the device. You can see below within the HTML file the inclusion of links to our JavaScript files in Resources/scripts and an embed tag which is required to grant us access to the system info service API. This provides a certain amount of info about the device itself such as battery charge level, network information and signal strength for example.
<html> <head> <link rel="stylesheet" type="text/css" href="Style.css" /> <script type="text/javascript" src="Resources/scripts/Main.js" /> <script type="text/javascript" src="Resources/scripts/Lib.js" /> <title>S60 UI Demo Widget</title> </head> <body onkeydown="keyDown(event)" onload="init()"> <embed id="systeminfo" type="application/x-systeminfo-widget" hidden="yes"></embed> <div id="Widget"> <div id="Title">S60 UI Demo Widget</div> <div id="InfoPane">Press a key</div> </div> </body> </html>
With the widget object API we can for example, enable the persistent storage of strings, change the navigation mode, or start other applications on the device, e.g. open the browser with a specific URL. All of these will be covered in more detail later on in the tutorial.
We also use the menu object and MenuItem object to create a menu tree for the widget.
You will also notice that we have added event triggers to the body element so that onkeydown events are captured by our JavaScript function keyDown(event) and loading up the widget calls our init() function below.
MOUSE_POINTER_NAVIGATION = false;
var sysInfo, networkName = "No Network";
// Called to initialize the widget
function init()
{
if (window.widget)
{
sysInfo = document.embeds[0];
networkName = sysInfo.networkname;
window.widget.setNavigationEnabled(MOUSE_POINTER_NAVIGATION);
createMenu();
if (window.widget.preferenceForKey("StoredText"))
$("InfoPane").firstChild.nodeValue = window.widget.preferenceForKey("StoredText");
}
}
The if statement makes sure that we only carry out these initial settings when the code is running as a widget on the device rather than during testing in a browser, which would cause an error as there is no widget object present in most browser platforms.
The name of the network that the device is currently subscribed to is retrieved from the system info service API, via the embed tag. setNavigationEnabled is set to false which has the effect of removing the mouse pointer as we want a navigation-pad driven interface. therefore we will catch the pressing of the navigation-pad in the onKeyDown function.
We then call a separate method to create the menu and finally check to see if there is any stored information for this widget with the key name of "StoredText". There will be none the first time the widget is initialised, but later we will store some info here and this can be retrieved even after the widget is exited and restarted. A very useful tool for persistent storage of settings or state information.
HIDE_SOFT_KEYS = false;
RIGHT_SOFT_KEY_LABEL = "Hello";
function createMenu()
{
var menuItem1 = new MenuItem("Menu item 1", 1100);
menuItem1.onSelect = menuItem1Selected;
window.menu.append(menuItem1);
var menuItem2 = new MenuItem("Menu item 2", 1200);
menuItem2.onSelect = menuItem2Selected;
window.menu.append(menuItem2);
window.menu.setRightSoftkeyLabel(RIGHT_SOFT_KEY_LABEL, rightSoftKeyPressed);
if (HIDE_SOFT_KEYS)
window.menu.hideSoftkeys();
window.menu.onShow = menuOnShow;
}
This function creates two MenuItem objects and attaches them to the widget's menu. The MenuItem objects can either be attributed separate functions to be called when they are selected (e.g. menuItem1Selected and menuItem2Selected, as in this case), or they can both call the same function wherupon the identity of the selected item is ascertained from the Integer ID argument passed to the menuItem (1100, or 1200) which is automatically passed as an argument to the trigger function.
function menuItem1Selected()
{
$("InfoPane").firstChild.nodeValue = "Menu Item 1";
if (window.widget)
window.widget.setPreferenceForKey("Menu Item 1", "StoredText");
}
function menuItem2Selected()
{
$("InfoPane").firstChild.nodeValue = "Menu Item 2";
if (window.widget)
window.widget.setPreferenceForKey("Menu Item 2", "StoredText");
}
function menuItemSelected(id) // alternative method
{
switch(id)
{
case 1100: menuItem1Selected(); break;
case 1200: menuItem2Selected(); break;
}
}
function keyDown(e)
{
//var keychar = String.fromCharCode(e.which); // alternative detection method
var keynum = e.which;
if (keynum == 37) navLeft();
else if (keynum == 38) navUp();
else if (keynum == 39) navRight();
else if (keynum == 40) navDown();
else if (keynum == 13 || keynum == 0) centreButtonPressed();
else if (keynum == 49) number1ButtonPressed();
else if (keynum == 50) number2ButtonPressed();
else if (keynum == 51) number3ButtonPressed(); // etc.
}
function navLeft()
{
$("InfoPane").firstChild.nodeValue = "Left";
if (window.widget)
window.widget.setPreferenceForKey("Left", "StoredText");
}
The keyDown function is called with every onkeydown event and this function determines which key was pressed and how to react. Note that both keynum 13 and 0 are both used in order to capture not only the centre select button but also the enter key on a keyboard while testing in a browser.
So if the left button is pressed then we retrieve the InfoPane element using the handy shortcut function from Lib.js, as shown below, and assign the string "Left" to its first child (text node) node value. Note that if the text node is empty then this may cause problems, so a space character is placed in empty HTML elements, e.g. <div> </div>.
function $(elementId) { return window.document.getElementById(elementId); }
Once we've retrieved the InfoPane element and displayed to the user the key that was pressed we then store the text string to demonstrate the storing of preferences, when the widget loads up the next time this preference String will be read and assigned to the InfoPane element.
This tutorial demonstrates the use of AJAX in widgets for retrieving online information. This is obviously an important part of widget development with access to online content being a key widget function. AJAX essentially enables the browser to retrieve Web content in the background without having to completely abandon the current page which would be the traditional model e.g. following a link. This is all enabled by the XMLHttpRequest object.
Before we get started, one important point to note is that testing of AJAX enabled widgets in desktop browsers is hampered by a common browser security feature called 'same origin policy' which sensibly restricts browsers to only fetching information from the same origin as the site currently loaded in the browser. For example if we load up a site from example.com this cannot then request information from another site via AJAX, only from example.com.
Widgets are not subject to these restrictions as widgets have no IP origin, they are stored locally, not on a server. However, for browser-based testing, the setup of an AJAX proxy is usually required. The proxy sits on the same server as the originator of the AJAX request (while testing this would be on the same computer as your local server). So rather than the AJAX request directly calling the 3rd party site it instead calls the proxy. The proxy then forwards the response, waits for a reply and delivers it back to the originator.
One alternative to an AJAX proxy is to use the Firefox plug-in Greasemonkey, which circumvents the security restrictions. But the proxy approach is much more general and allows the use of browsers other than just Firefox. Testing with the Safari browser is often recommended as this is based on the WebKit rendering engine, as is the S60 WRT (albeit a different version, which we'll discuss later).
This example widget calls a method in the Flickr REST API to return an XML file with a list of interesting photos. Therefore to get this working will require a valid Flickr API key. This is a usual procedure when sites provide APIs so they can track who is using them. Registration is usually free (for low-level, non-commercial usage) and fairly painless. Or you can get a temporary key by going here calling the method and copying the api_key element's numerical text String.
Once you have a key then place it in the code as below...
API_KEY = "5df2634909217543307140ac17616cc5";
var imgRefRequest;
var attributesArray = new Array();
// Called to initialize the widget
function init()
{
getImageRef("http://api.flickr.com/services/rest/?method=flickr.interestingness.getList&api_key=" + API_KEY);
}
For our AJAX request we will use a wrapper object called AJAXBroker (defined in the AJAXBroker.js JavaScript file) for the XMLHttpRequest object. This takes care of all the AJAX code for us, we only have to create a new AJAXBroker object and create two functions, one to be called if the request is successful, one to be called if there is a failure in the request. before we make the call we set up two other variables; imgRefRequest is the object which will be our new AJAX wrapper object and attributesArray will be used to store the results of our request.
In the init() function we call our getImageRef function with the URI of the Flickr flickr.interestingness.getList method.
function getImageRef(uri)
{
imgRefRequest = new AJAXBroker(uri, null, null, getImageRefSuccess, getImageRefFailure);
imgRefRequest.open();
}
As you can see above the AJAXBroker object is initialised with five arguments, these are detailed below...
AJAXBroker(uri, postDataContentType, postData, successListener, failureListener)
The first argument is the URI of the call. The second and third arguments relate to whether the call is a GET or POST. If it's a POST request then the second argument is the content type of any POST data, and the third argument is the POST data itself. If the request required is a GET then both arguments are null, as in our widget. The last two arguments are call-back functions. The first will be called on successful completion of the request, the second if there are any problems.
We then define the success and failure functions as below...
function getImageRefSuccess() {
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(imgRefRequest.getResponseText(), "text/xml");
// documentElement always represents the root node
if (xmlDoc.documentElement.getElementsByTagName("photos"))
{
var photoArray = xmlDoc.documentElement.getElementsByTagName("photo");
attributesArray["farm"] = photoArray[0].getAttribute("farm");
attributesArray["server"] = photoArray[0].getAttribute("server");
attributesArray["id"] = photoArray[0].getAttribute("id");
attributesArray["secret"] = photoArray[0].getAttribute("secret");
attributesArray["title"] = photoArray[0].getAttribute("title");
$("Loader").style.display = "none";
$("NewImage").src = "http://farm" + attributesArray["farm"] + ".static.flickr.com/"
+ attributesArray["server"] + "/" + attributesArray["id"] + "_"
+ attributesArray["secret"] + "_s.jpg";
$("InfoPane").firstChild.nodeValue = attributesArray["title"];
}
else
{
getImageRefFailure();
}
}
function getImageRefFailure()
{
$("Loader").style.display = "none";
$("InfoPane").firstChild.nodeValue = "Loading failed";
}
If the request was successful we create a new DOMParser object to deal with the resultant XML returned to us, passing it the text (XML) contained in imgRefRequest.getResponseText(). Then from the documentElement of the xmlDoc we have access to the rest of the XML by calling DOM methods, for example getElementsByTagName to return an array of element objects. We then choose which element of the array we want and either call getAttribute to get an attribute node or firstChild.nodeValue to get a text node belonging to that element and store the result in an associative array. So by way of an example the following...
<photos><photo farm="1">text node</photo>...</photos> photoArray[0].getAttribute("farm")
...returns "1" and...
photoArray[0].firstChild.nodeValue
...would return "text node".
Once we have the information we need from the XML we remove the loading image and construct the photo's URI as detailed here. This is then used as the source of the image element, and the title of the photo is added to the InfoPane element.
Animating elements within a widget can dramatically improve the look and feel of a widget. The Yahoo desktop widget engine supplies various methods for animating elements within their widgets. This widget tutorial demonstrates the use of simple mathematical function to calculate the step size of an incremental movement from a start location to a destination location.
However, using JavaScript for this is a rather heavy-handed approach and given the limited performance of the device platform the animation is course not as smooth as one might like. On a positive note the use of JavaScript for animation may become obsolete if the latest version of WebKit is anything to go by. If you haven't already download the latest version of Safari do so, and have a look at some CSS animations, transforms and translations enabled simply through HTML and CSS. However, until S60 adopt the latest version of WebKit JavaScript animation will have to do for the time being.
The first thing to note is that the animation script can only move objects that are defined with positional information.
#AnimatedElement
{
position: absolute; /* Important for the animation, */
left: 15px; /* if the element is not positioned... */
top: 60px; /* ...then cannot get start positions */
width: 20px;
height: 20px;
background-color: #C00;
}
The animation can either be described as movement to a destination location (x,y), or as a translation by a given distance in the x and y plane. The corresponding animation functions and their arguments are shown below.
moveElementTo(element, posX, posY, duration, rate, ease, callback) moveElementBy(element, distanceX, distanceY, duration, rate, ease, callback)
Positions and distances are in pixels, duration is the time taken for the animation and is given in milliseconds.
The rate is the number of movements per second (equivalent to fps). The maximum rate is ultimately dependent on the capabilities of the device. Smooth animation requires about 25 fps, which works well on a desktop browser but is generally not possible on a device (redrawing an element is quite a heavy operation - and I image depends on what the element contains). so I find that 12 fps works about the best on S60 devices that I have tested.
The ease argument is there to add a little more realism to the animation. Ease can either speed up the animation at the start (ease out) or slow down at the end (ease in), or both. This argument is passed a String value of either "EaseOutIn", "EaseOut" or "EaseIn", anything else is interpreted as no ease (linear) animation.
The ease in and ease out are calculated via a simple linear function both at the beginning of the motion to ease out, and at the end to ease in. Of course there are better ways to achieve the same results.
Finally the callback argument requires a function, which is then called once the animation has completed, or null if no call-back is required.
If any of the other arguments are passed null then the element's current position in that dimension is maintained. Defaults are assumed for the rest of the arguments as below.
DEFAULT_ANIMATION_DURATION = 250; DEFAULT_ANIMATION_RATE = 12;
A numerical ease can also be given, any number in the range 0 < num < 0.5. this then describes what proportion of the animation the ease should apply to (applies to both out and in), for those who are interested the default is...
DEFAULT_ANIMATION_EASE = 0.3;
Of course repeatedly calling the animation functions for a given element whilst the element is still moving (deliberately) won't have any effect so to avoid this we can check the status of the animation with the following functions...
stillMoving(element) moveCompleted(element)
So in our animation demo widget we have the following code...
function navLeft()
{
if (!left && moveCompleted($("AnimatedElement")))
{
moveElementBy($("AnimatedElement"), -130, 0, 500, fps, easeStr, finishedMove);
left = true;
}
}
function finishedMove()
{
$("InfoPane").innerHTML = "Left: " + $("AnimatedElement").style.left + ", Top: "
+ $("AnimatedElement").style.top + "<br />" + fps + "fps, " + easeStr;
}
So that if the element is not already on the left and is not currently in motion, then move the element left by 130 pixels and call finishedMove once completed, which updates InfoPane with the current coordinates of the animated element.
Note that the top or left position information only appears in InfoPane once it has been set by the animation (element.style.left = leftPos + "px";).
Now to put all these concepts together into a working widget!
The concept of an RSS feed reader is a simple one in that it requests a feed and displays it to the user in an appropriate manner. This widget requests feeds and displays the feed item titles as a vertical list on the main widget panel, letting the user scroll and select a title. This opens up a secondary panel which is populated with the item's associated information.
The HTML looks like this...
<div id="Widget">
<div id="Title">RSS Feed Reader Demo Widget</div>
<div id="MainPanel">
<ul id="ListPanel"></ul>
<div id="ItemPanel">
<div id="ItemTitle"> </div>
<div id="ItemSubTitle"> </div>
<div id="ItemText">
<div id="ItemTextScrollPanel"></div>
</div>
<div id="ItemLinks">
<span id="ItemBack" class="linkHighlighted"
onclick="javascript:closeItem();">Back</span> 
<span id="ItemOpen"
onclick="javascript:openLinkInNewWindow(this.getAttribute('linkuri'));">
Read more</span>
</div>
</div>
<div id="Loader">Loading
<img id="LoaderImg" src="Resources/images/loader.gif" />
</div>
</div>
<div id="InfoPane"> </div>
</div>
This shows the ListPanel ul element which will contain the titles as li elements. These will be constructed on the fly as we have no idea how many list items we will need. The secondary panel ItemPanel is also defined here and this will contain the items title, published date, main content, and a link to the full story.
The first important thing to note here is that in the successful feed response the information required is held as text nodes in the XML not attributes as in the previous AJAX example. Therefore we use the following code to extract the information we require from the XML...
itemsArray[0][i] = items[i].getElementsByTagName("title")[0].firstChild.nodeValue;
The second important thing to note is the use of a new function constructElement from our Lib.js script to create the li elements on the fly. Once the feed is returned to the widget this function creates the list of titles.
function createFeedItems()
{
for (var i=0; i<itemsArray[0].length; i++)
{
if (i %2)
var newListItem = constructElement("li", $("ListPanel"), null, "class", "odd");
else
var newListItem = constructElement("li", $("ListPanel"), null, "class", "even");
newListItem.innerHTML = itemsArray[0][i];
}
$("ListPanel").firstChild.setAttribute("class", "highlighted");
}
So for each item in the array, construct a li element using the following function.
function constructElement(type, parent, text) // plus any number of optional attribute name and data pairs as arguments
{
var newElement = window.document.createElement(type);
if (text) newElement.appendChild(window.document.createTextNode(text));
for (var i=3; i<arguments.length; i+=2)
{
if (arguments[i + 1]) newElement.setAttribute(arguments[i], arguments[i + 1]);
}
parent.appendChild(newElement);
return newElement;
}
Each li element is created, and then added as a child to the ListPanel element. The li elements are given the class attribute "odd" or "even", maintaining the alternating background colour of each list item.
As it stands the user interface relies mainly on the use of the navigation-pad. The user selects up/down to move the highlight up and down the list of items and presses select to view that item. The scrollMode Boolean keeps track of whether we are viewing the scrollable list or viewing the list item panel (ordinarily we could attach a mode attribute to the body of the document, and then use attribute selectors in CSS, however this doesn't appear to be supported yet in the WRT).
The highlightPos variable keeps track of the position of the highlight and the following code changes the class of the list item as the user moves through the list depending on whether the list item is highlighted or odd/even (non-highlighted). Again if attribute selectors were supported in CSS we could avoid changing the entire class attribute of the element.
if (highlightPos %2)
$("ListPanel").childNodes[highlightPos].setAttribute("class", "odd");
else
$("ListPanel").childNodes[highlightPos].setAttribute("class", "even");
highlightPos--;
$("ListPanel").childNodes[highlightPos].setAttribute("class", "highlighted");
So once an item has been chosen the user presses select and the item is presented to the user.
function openItem(num)
{
$("ItemTitle").innerHTML = itemsArray[0][num];
$("ItemSubTitle").firstChild.nodeValue = itemsArray[1][num];
$("ItemOpen").setAttribute("linkuri", itemsArray[2][num]);
$("ItemTextScrollPanel").innerHTML = itemsArray[3][num];
$("ItemPanel").style.display = "block";
scrollMode = false;
}
Some RSS feeds provide the main item content in text, others in HTML. If the item story text/html is bigger than the viewable area the user can scroll up or down with the navigation pad. If HTML is present the user can use the right soft key to change navigation mode bringing the mouse pointer into play in order to select links in the HTML.
Note that there is a major issue here in that this will open links in the widget, not in the browser. To fix this we need to either parse the HTML to change all the links so that they call the method openURL, or perhaps add a 'Back' menu item and query the location.history to enable a back function so the widget behaves just as a browser would.
The user can go back to the title list by either choosing the 'Back' link with the pointer or if no other links are highlighted on the screen and in navigation mode then the user can press select once the back link is underlined (left/right changes the underline between the back link and the read more). Choosing read more opens up the link from the feed item in the browser using the widget.openURL function.
function closeItem()
{
$("ItemPanel").style.display = "none";
if (navMode)
{
if (window.widget)
window.widget.setNavigationEnabled(MOUSE_POINTER_NAVIGATION);
navMode = false;
goBack = true;
$("ItemBack").setAttribute("class", "linkHighlighted")
$("ItemOpen").removeAttribute("class")
}
scrollMode = true;
$("ItemTextScrollPanel").style.top = "0px";
}
The feed is changed using the widgets menu.
function changeFeed(num)
{
loadedFeed = false;
if (window.widget)
window.widget.setPreferenceForKey(num + "", "FeedNumber");
itemsArray = new Array([], [], [], []);
scrollPos = 0, highlightPos = 0, goBack = true;
removeFeedItems();
$("ListPanel").style.top = "1px";
closeItem();
$("Title").firstChild.nodeValue = "RSS Feed Reader Demo Widget";
$("Title").style.backgroundImage = "";
$("InfoPane").firstChild.nodeValue = " ";
$("Loader").style.display = "block";
getFeed(feedsArray[num]);
}
When changing the feed the old items are removed with the following...
function removeFeedItems()
{
while($("ListPanel").firstChild)
{
$("ListPanel").removeChild($("ListPanel").firstChild);
}
}
Of course the links problem mentioned above is just one of the issues with this widget, it is by no means perfect, so perhaps you'd like to have a go at solving a few of these issues as an exercise in widget coding!
li elements is hidden and no indication is given (we would normally use text-overflow: ellipsis;, but this is not yet supported