Introduction
The Folding@Home User Stats Gadget for Vista accesses the XML data feed from ExtremeOverClockers' folding website. It formats the XML in a fashion similar to the sigimages provided by the EOC site.
Background
Folding@Home is a distributed computing project run by the Pande Group at Stanford University. You can find more information at the project website.
Using the code
The code provided was originally written for use in Vista Gadgets, and so can be used like any other Vista Gadget. Functionally, it is a simple XML parser written in JavaScript, which gathers, identifies, and assigns values to the appropriate locations in the HTML.
It was designed to be portable; as such, the Gadget Settings paradigm is encapsulated and can be replaced with other settings storage methodologies. Two ideas already have been to embed this into Google's desktop widgets, or to implement it for Konfabulator.
The visual representation is pretty simple - it uses a table to organize the content that we'll eventually fetch from the XML data source. The items in the table were named after the values expected to be found in the XML. The background image is a cropped & alpha-ed version of an image found on the Folding@Home website.
<body background="fah.png" onload="showXML();interval = setInterval('showXML()', 900000);"
onunload="clearInterval(interval);">
<table id="gadgetContent">
<tr>
<th colspan=2><b><span id="User_Name" /></b></th>
</tr>
<tr>
<td><u>Team Rank</u>:<br><span id="Team_Rank" /></td>
<td><u>Points</u>:<br><span id="Points" /></td>
</tr>
<tr>
<td><u>Change 7d</u>:<br><span id="Change_Rank_7days" /></td>
<td><u>24hr Avg</u>:<br><span id="Points_24hr_Avg" /></td>
</tr>
<tr>
<td><u>User Rank</u>:<br><span id="Overall_Rank" /></td>
<td><u>Today</u>:<br><span id="Points_Today" /></td>
</tr>
</table>
<span id="errorText" />
</body>
I used setInterval
rather than setTimeout
. There wasn't a performance difference, and I liked the name more.
I think error handling is important, so I'm going to mention that prior to the actual functionality.
The error handling for the Gadget was managed by the main process, showXML()
, but there were some helper functions for managing the interface. Also, the helper function for getting the Gadget settings does not handle its own errors. Instead, it passes the buck back to the main process if it identifies any problems. I appropriated the RangeError
exception for this, but not for any good reason.
function setGadgetProperties()
{
username = System.Gadget.Settings.read("username");
teamID = System.Gadget.Settings.read("teamID");
if ( username.length == 0 || teamID.length == 0)
throw RangeError("Have you set your options?");
}
function displayError(message)
{
errorText.innerHTML = message;
gadgetContent.style.display = "none";
}
function clearError()
{
errorText.innerText = "";
gadgetContent.style.display = "";
}
The primary code block in ShowXML()
is wrapped completely within a try
block. The catch
is below. Given that the target audience for this Gadget may or may not have any particular technical prowess, I wanted to keep the messages simple and to the point, but clear as to what's likely happening.
if ( e instanceof TypeError )
{
if ( e.message.indexOf("documentElement") >= 0 )
displayError("Did you spell your name right?");
else if ( e.message.indexOf("resource") >= 0 )
displayError("Are you connected to the internet?");
else
displayError("Quick, tell someone!<br>" + e.message);
}
else if ( e instanceof RangeError )
displayError(e.message);
else
displayError("Something's wrong.<br>Restart this gadget instance.");
With that out of the way, let's look at ShowXML()
. We'll approach it in 3 parts.
Part 1: Get the data
Jason over at EOC does a regular pull from the Folding@Home reporting servers, collating the data into something usable. He has made an XML data source available to folks, so that's what we're leveraging for this Gadget. To get the data our user wants to see, we need to get our user's input first, so that we can tell Jason what information we want.
There's nothing much tricky going on here, but see Point of Interest #1 to learn something you might not know about MSXML2.XMLHTTP
.
setGadgetProperties();
clearError();
xmlhttp.open("GET", <a href="">http:
+ username + "&t=" + teamID,false);
xmlhttp.send(null);
Part 2: Organize the data
The XML returns with Team
and User
information. I was only interested in the User information, so that's all I captured. But there's no reason why someone could not also capture the Team info, and one could easily write a similar insertTeamXML()
function to do the same thing for a team table.
xmldoc = xmlhttp.responseXML;
userxml.async = false;
userxml.loadXML(xmldoc.documentElement.getElementsByTagName("user")[0].xml);
insertXML(userxml.documentElement);
Part 3: Display the data
insertUserXML()
encapsulates the manipulation of the gadgetContent
table. Note: If there's a better way to capture the data in the XML based on the span IDs in the HTML, I'd love to hear it - the more flexible, the better.
User_Name.innerText = data.getElementsByTagName("User_Name")[0].text;
if ( User_Name.innerText.length < 10 )
User_Name.style.fontSize = 'medium';
else if ( User_Name.innerText.length < 16 )
User_Name.style.fontSize = 'small';
else if ( User_Name.innerText.length < 22 )
User_Name.style.fontSize = 'x-small';
Team_Rank.innerText = data.getElementsByTagName("Team_Rank")[0].text;
Points.innerText = data.getElementsByTagName("Points")[0].text;
Change_Rank_7days.innerText = data.getElementsByTagName("Change_Rank_7days")[0].text;
Points_24hr_Avg.innerText = data.getElementsByTagName("Points_24hr_Avg")[0].text;
Overall_Rank.innerText = data.getElementsByTagName("Overall_Rank")[0].text;
Points_Today.innerText = data.getElementsByTagName("Points_Today")[0].text;
if ( Change_Rank_7days.innerText.charAt(0) != '-'
&& Change_Rank_7days.innerText.charAt(0) != '0')
{
Change_Rank_7days.innerText = "+" + Change_Rank_7days.innerText;
}
In all current cases (though the PS3 users might prove this wrong eventually...), the pre-set font size for all of the values worked okay. However, since usernames are extremely variable, I had to make sure that the username would display properly; to that end, I hacked a "best guess" for name sizes. So far, it looks like I guessed pretty well.
Also, I wanted to emulate the +/- display for the Change_Rank_7days
that happens in the forum sigimages that EOC can generate. This was purely aesthetic, but I think it's something that would have been missed if it wasn't included.
The Settings Page is so utterly boring, it's barely worth a mention. That's why I left it for the end. However, it's the place where the Vista-specific coding is happening, so we'll glance at it.
<body onload="showSettings();">
<label for="username">Username:</label><br>
<input type="text" name="username" id="username" length="40" /><br>
<label for="teamID">Team ID#:</label><br>
<input type="text" name="teamID" id="teamID" length="40" />
</body>
The body is just two fields for user input. When the Gadget is first launched, the users will have to come here first to set up their information. Once the information is set, however, as long as they don't manually close the Gadget, the information will be maintained on any subsequent shutdowns or reboots. See Point of Interest #2 for more information.
The script below simply shows how the Gadget settings are loaded when the window opens (see the onload event from the HTML body), and saved when the window is closed (note the event handler assignment at the top.
System.Gadget.onSettingsClosing = settingsClosing;
function showSettings()
{
username.value = System.Gadget.Settings.read("username");
teamID.value = System.Gadget.Settings.read("teamID");
}
function settingsClosing(event)
{
if (event.closeAction == event.Action.commit)
{
System.Gadget.Settings.write("username", username.value);
System.Gadget.Settings.write("teamID", teamID.value);
}
}
Points of Interest
MSXML2.XMLHTTP
objects will cache their results. This can be good AND bad, since cached might cause confusion when a user's internet service provider is inaccessible. - Vista stores a list of the configurations for the currently-running Gadgets in a Settings.ini file in the Windows Sidebar directory. These settings will be specific to every user. This is likely the physical location from which the
System.Gadget.Settings
interface reads/writes.
History
- 1.1.1 - Minor modifications for CodeProject - no functional changes
- 1.1.0 - Improved error handling (thanks to totoro for trial & feedback!)
- 1.0.0 - Launched to TechReport's Distributed Computing Forum (go Team 2630!)