The goal of this article will take you through the process of creating a Vista Sidebar Gadget for the first time. I have included just about all the steps you need to go through to create any gadget. Plus, I've tried to provide background on each step and point out the Gotchas that I ran into as I was creating this Sidebar Gadget for this article.
As a disclaimer, this article is a bit of a shameless plug for the company I work for because we sell professional sports photos online. But I figured it would be ok, because the point of the article is to show how you can integrate a Sidebar Gadget with your online content. For example, you could take this gadget and adapt it to consume an image feed of the current sale items, or best deals in your online catalog. If the user sees something they like they click the "buy" link and can purchase the item!
What is a Sidebar Gadget?
The Windows Vista Sidebar is an executable that runs on the desktop of the new Windows Vista OS. It hosts "Gadgets" which are small DHTML applications which have almost all the capabilities of a web page running in Internet Explorer. (I say almost all because this is a new technology and it's my first attempt at using it.)
Windows Vista comes with a small sampling of Sidebar Gadgets to give you an idea of what is possible. Some seem pretty useful:
- An analog clock
- An RSS news reader
- A CPU / memory monitor
"What's so useful about an analog clock" you say? Well, it's ok, but what makes it useful is that the Sidebar is capable of running multiple, independent instances of each gadget. And each instance has it's own settings. So if you work in a different time zone than your clients or compatriots you can run a separate instance of the clock for each time zone! Of course your desktop real-estate is limited, but the sidebar is capable of displaying multiple pages of gadgets, so you can navigate through the ones you have open.
A minimal Gadget application consists of the following items:
- An XML manifest file named gadget.xml (yes, the name is a requirement)
- An HTML file
- An "icon" (jpg, gif or png) file
A Gadget application may also include the following items:
- script files (.vbs or .js)
- stylesheet files (.css)
- a settings HTML file
- a "flyout" HTML file
- globalization files
- ActiveX components
Gadgets have two installation directories:
- %USER_DATA%\Local\Microsoft\Windows Sidebar\Gadgets - for user gadgets
- %SYSTEM_ROOT%\Program Files\Windows Sidebar\Gadgets - for global gadgets
Gadget Development Tools & Resources
window.alert() attempts failed from the get go, I was dead in the water right out of the gate.
So I installed my IDE of choice, Visual Studio 2005, configured IE for script debugging and instantly determined the cause of my first problem and every problem thereafter. If you can do it all in notepad, more power to you. You're more of a man than I. But after my first experience, I don't recommend flying blind.
You don't need to be running Windows Vista either, but I don't recommend that either since you can't really test your gadget without it. I don't know about you, but I'm not quite ready to take that plunge. So I installed Vista as a virtual machine on Virtual Server. For this article, I am working with Windows Vista Ultimate Edition, RC2 Build 5744. The final release is out by now (or at least it's available on MSDN) so you can work with a more current version, but everything seemed to be working fine for me so there was no reason to download and install the entire OS all over again.
An important note about updating the installed gadget files. I found that if I'm just updating the .htm file for the Flyout or Settings windows, I can update the file and the next time the window loads it will display the updates. However, if I'm modifying a .js, .css or the main Gadget.htm file, then I have to close all running instances of the Gadget before I can open a new instance and view the changes.
Here are a list of resources I found helpful along the way:
Step 1: The manifest File
The manifest file describes your Sidebar Gadget and contains the settings required by Windows Sidebar to both display it in the Sidebar Gallery and create an instance of it in the Sidebar.
<name>MaxPreps Gallery Viewer</name>
<author name="Mark J. Miller">
<info url="http://developmentalmadness.blogspot.com"<BR> text="Mark J. Miller's blog"/>
<description>View high school sport's action photos!</description>
<icon height="150" width="150" <BR> src="images/icons/MaxPreps_Blk_130w.gif" />
<base type="HTML" apiVersion="1.0.0" src="gadget.htm" />
<platform minPlatformVersion="0.3" />
<defaultImage src="images/icons/MaxPreps_Blk_130w.gif" />
There isn't really any documentation on the gadget.xml file schema, all the tutorials simply display an example of the manifest file. It's up to the reader to just copy & paste, then edit the sample. But I'll try and give you a summary here of what I've found.
Except for the
hosts element and it's children, most of the schema is used to describe your Sidebar Gadget for the Gadget Gallery. The elements
icons are used to display an application icon above the name of your Sidebar Gadget in the Gadget Gallery. While
description all are used by the details pane to describe your Sidebar Gadget. The
info element has two attributes:
url is exactly what it says it is. The
text attribute is optional, but if you want to display something descriptive in place of the url you can use it. The only one I haven't been able to account for is
namespace, but by the time I realized it, I figured I had already come up with a namespace and entered it, so I decided to leave it.
Because of the lack of documentation, I'm guessing on some of this, but most of what is below the
hosts element stays as it is. However, the
src attribute of
base indicates the main HTML file for your Sidebar Gadget and
defaultImage allows you to specify a drag icon used when you drag your Sidebar Gadget from the Gadget Gallery to the Sidebar. This can be a transparent png and the transparency should be preserved as you drag the icon to the Sidebar. However, I am by no means a graphic artist, so I did not attempt to make my own graphic to test this.
Step 2: The Settings Page
As you create your gadget, it should save you some time if you create your settings page next. The reason I say this is because I did not, and I found that I would have to go back through much of my code that is already in place to accommodate each configuration setting I add. Because I found myself running out of time, I settled for keeping the configuration settings to a single simple option so I could have time to write this article. So unless you are disciplined enough to spec out your entire first project before starting it I recommend building your settings page first. Then as you build the rest of your Sidebar Gadget you can quickly add in settings as you write the application code and you won't have to go back and rewrite sections to accommodate new settings.
Each page (gadget, settings, flyout) is independent and does not share variables. So the Sidebar Gadget object model contains an
System.Gadget.Settings object which allows you to persist data while your Sidebar Gadget is running. It is very useful for saving state data and communicating between the different pages of your Sidebar Gadget. Your settings page is a means to allow users to make configuration changes to your Gadget and persist those during the current session.
A settings page is not required, but when you do create one you will see the above icon included below the close button next to your gadget. And when you click on it you'll get something like the image below. As I said before, I am not a graphic designer, so the part I appreciate about the settings page is it's simplicity. Even though you need to include a full HTML page (
BODY elements) the only other thing you need is to add a
STYLE element to designate the height and width of the body and then drop a couple HTML controls onto the page. Everything around the page, including the "OK" and "Cancel" buttons and their functionality comes pre-built as part of the Sidebar Gadget package. I chose to go one step further and add a
DIV tag as a place holder for validation errors on the page.
List size (#):<input type="text" id="maxCount" size="3" />
<div id="errorMessage" <BR> style="color:Red;font-size:12pt;font-family:Calibri;"> </div>
A lesson I learned is that certain small details can cause a lot of headaches. When I created my first HTML page for this project I added a
SCRIPT tag to the
HEAD element to reference an external
.js file. But I couldn't figure out why the Sidebar Gadget wouldn't load. (This was when I decided I needed an IDE with script debugging capability if I wanted to continue with this endeavor). It turns out I could not use the following format for my script tag:
SCRIPT tags use this format:
In order to use the settings page you'll need the following two lines of code which will be called from your main page:
System.Gadget.settingsUI = "settings.htm";
System.Gadget.onSettingsClosed = settingsUpdated;
The first line should be called from your
onload event to tell the Sidebar you are using a settings page. This will add the settings button next to your gadget. When the user clicks this button, it will load your settings page. The second line indicates a method (in this case
settingsUpdated) to be called when the settings page successfully closes. It will allow you to read the new settings and update the behavior or layout of your Sidebar Gadget.
System.Gadget.onSettingsClosing = onClose;
var sMaxCount = System.Gadget.Settings.read(SETTING_MAX_GALLERY_COUNT);
if(sMaxCount != "")
gMaxCount = parseInt(sMaxCount);
maxCount.value = gMaxCount;
When your settings page loads, you'll need to do at least two things: set the
onSettingsClosing event handler, and read the current settings values to display them in the controls on the settings UI.
The first is straightforward, just create a function with the following signature and pass it to the onSettingsClosing delegate:
function (parameter1). The name of the function and the parameter are up to you but here's what my function looks like:
if(event.closeAction == event.Action.commit)
errorMessage.innerHTML = "Please enter an integer value.";
event.cancel = true;
gMaxCount = parseInt(maxCount.value);
event.cancel = false;
The parameter passed to your event handler is the event arguments. This is another case where I did not find documentation other than what was in the code samples I came across. If anyone finds documentation, I will gladly include it in my resource links. The
event.Action property has two possible values:
cancel. They correspond to the "Ok" and "Cancel" buttons on the settings UI.
In this case, I check to see if the user clicked the "OK" button (
Action.commit) and then validate the data to make sure the value is numeric. If it isn't, I display an error message and set
event.cancel = true so that the user will be returned to the settings page to either cancel the operation or correct the problem and resave the settings. If everything is valid, I save the settings and set
event.cancel = false so the settings page will close and the user will be returned to my Sidebar Gadget.
When you want to persist your settings you have two methods:
write(string name, obj value) and
writeString(string name, obj value). When you use
write the Settings object will try and guess the type of your value. If your value is a string, use
writeString to eliminate the guesswork when you can.
This brings us to the second step I mentioned: reading the current settings. The
writeString methods each have corresponding read methods:
read(string name) and
readString(string name). When you read from the Settings object, make sure and check the value for an empty string (
""). If the setting is empty or has not been set, it will return and empty string instead of a null value. When using boolean values, a tip I learned from a tutorial I read was to cast the value to force it as boolean. You can cast it like this:
var valueFromSettings = System.Gadget.Settings.read("myValue");
var myBool = !!valueFromSettings;
Now lest move on to the actual Gadget functionality
Step 3: The Gadget UI
My goal in creating this gadget was to duplicate the RSS Reader gadget's functionality and pull an XML feed which I would use as a source to build a list of the most recent photo galleries on my site. Something I hadn't mentioned yet, is that because of the architecture of Sidebar Gadgets you can open up any Gadget on your system and view the source code, just like if you were viewing a web page in IE. No, I don't mean you can right-click and select "view source...". But you can navigate to the directory where the gadget is located and view the source files. So when I got started I opened up the RSS Reader and realized they were using the Feed Store built-in to IE. And since I would not be using RSS, Atom or any other standard schema for my XML I had to turn elsewhere.
After some playing around with XSLT, I opted to go with AJAX instead because it would make certain features easier to implement. There are some good AJAX tutorials on the site I used when I used AJAX for the first time, so do a search and read a few of them if you're not familiar with the technology yet.
The next decision I had to make was how to make the XML feed available to the gadget. So in order to simplify things and allow others to use the source code for this gadget on their local machines, I decided to create two static XML files and put them on my local web server. I have included these two xml files and Feed.xml and Feed2.xml in the source files included with the article. You can place them on your local web server or a remote one it doesn't matter. As I was building this gadget, I had the gadget installed on my Vista virtual machine and my feed files stored in my IIS virtual directory on Windows XP. When I wanted to test the auto update ability, I just swapped the files back and forth to imitate a dynamic process.
For brevity sake I haven't included the actual AJAX code here, just the pertinent stuff:
var sMaxCount = System.Gadget.Settings.read(SETTING_MAX_GALLERY_COUNT);
if( sMaxCount != "" )
gListMax = parseInt(sMaxCount);
gResizeGadget = true;
var ajaxDoc = gRequest.responseXML;
gXmlDoc = ajaxDoc;
if(gAjaxType == AJAX_TYPE_GALLERIES)
setTimeout("refreshData()", 5 * 60000);
setTimeout("refreshData()", 1 * 60000);
var dt = new Date();
makeXmlRequest(DATA_RESOURCE_URI + "?t=" + dt.getTime(), <BR> AJAX_TYPE_GALLERIES);
To tie the missing stuff together here,
makeXmlRequest which calls
getXmlContent when it completes. Then
getXmlContent stores the XML in a global variable and calls
printGalleries which uses the global variable to read the XML and build the list on the gadget UI. I haven't included it here because of it's length and there's nothing in
printGalleries which is unique to the Sidebar Gadget API. Then a timer is set to requery the remote server for updates to the feed by calling
A design aspect that you will be forced to deal with is the limited space you are given for your gadget. The width of the Sidebar is 130 pixels, which is not much space for anything. But certainly in our case there isn't much room for text. Because I'm pretty new to DHTML, my first attempt to deal with this was to count up the maximum number of characters I could fit using the current font, then use the
substring method to chop that string and append an ellipsis(...). But I couldn't get it to look as neat as the RSS Feed reader Gadget. So again, I opened up the code to see what they had done. Now those of you who are more experienced with CSS and DHTML may have seen this one coming a mile away, but bear with me here.
var newRow = table.insertRow(table.rows.length);
var cell = newRow.insertCell(0);
var html = "<DIV id=\"item" + i + "\" title=\"" + node.getAttribute("name")
html += "\" onClick=\"loadFlyout('" + node.getAttribute("id");<BR> html += "');this.blur();\"";
html += " onmouseover=\"this.style.color='Red'\"";<BR> html += " onmouseout=\"this.style.color=''\"";
html += " style=\"font-size:13px;margin-bottom:0px;margin-top:0px;\"> ";<BR> html += node.getAttribute("name") + "</DIV> ";
html += "<DIV id=\"sport" + i + "\" style=\"color:White;font-size:11px;";<BR> html += " color:#67788a;margin-top:0px;margin-bottom:0px;\"> " ;
html += node.getAttribute("sport") + "</DIV> ";
cell.innerHTML = html;
eval("item" + i).style.textOverflow = "ellipsis";
eval("item" + i).style.overflow = "hidden";
eval("item" + i).style.whiteSpace = "nowrap";
eval("item" + i).style.width = 115;
eval("sport" + i).style.textOverflow = "ellipsis";
eval("sport" + i).style.overflow = "hidden";
eval("sport" + i).style.whiteSpace = "nowrap";
eval("sport" + i).style.width = 115;
eval("sport" + i).style.borderBottom = "dotted 1px White";
There are two important steps here to point out. First, you must be sure to set the width of the HTML element which will act as the container for your text. In this case I am using a
DIV tag within a
TD element. So I have set the width of the
DIV tag. Then for each of the text elements, I set
whiteSpace style properties. You don't have to set them in any particular order, as you can see above, but they each need to be set to get the effect of the text running off the side of the page.
So now we have a Gadget which reads from a remote XML feed and then displays a list of the items in the feed. For navigation controls, I borrowed the graphics used by the RSS Reader Gadget. They include previous and next buttons, and a counter to display the start and end index of the items currently in the list. Let's move on now to what we can now do with that data.
Step 4: The Flyout
The flyout is an optional component of the Sidebar Gadget, but it's great when you need more room to display your application. The only limit to the size of your flyout is that of the user's display resolution. With the graphics capabilities required by Windows Vista in the first place it's probably safe to assume that your users are going to have a minimum of 1024x768 resolution.
To open your flyout only takes two lines of code. You need to specify the HTML file used by the flyout and set the
show property to true. Like this:
System.Gadget.Flyout.file = "flyout.htm";
System.Gadget.Flyout.show = true;
Where you place these commands isn't so important as the order they are in, before you display the flyout you must indicate the file to be used. You also have the option of registering a method with either or both of the
onHide events. Keep in mind that the Gadget page and the Flyout are independent and cannot communicate directly with one another. This means that the
onHide events are used for updating the Gadget UI or trigger some behavior in the main Gadget page based on those events. We'll come back to these events in a minute.
The best way to communicate with the flyout is to use the
System.Gadget.Settings object. In this case we use it to store the id of the gallery that was clicked on the Gadget so the flyout can read from the XML and display the images. Here's where we hit a bit of a snag.
The XML feed was too large to pass into the settings object. Whenever I tried to write the XML as a string, it was unsuccessful. There were no errors, the program just kept running as if everything was ok. But when the flyout tried to read the XML from
System.Gadget.Settings.readString the result was an empty string. I tried inspecting the settings object from the Gadget as soon as I had written the XML, but still the value wasn't getting stored. I knew I was doing everything correctly because I could read the gallery id value I had passed, just not the XML.
To work around this I decided to write the XML to disk, then I could read it in from the flyout. The next obstacle was that the Sidebar object model has no read and write capability without user interaction. If you want to open or save a file the
System.Shell object and it's children provide methods to ask the user where they want to save a file, or which file to open. And it has the ability to inspect the file system objects and to create and delete folders, but not to open files as text or write to them behind the scenes. However, this problem was easily solved by using the
gReadingXml = true;
var path = getDataPath();
var fso = new ActiveXObject("Scripting.FileSystemObject");
var output = fso.OpenTextFile(path,2,true);
gReadingXml = false;
var fso = new ActiveXObject("Scripting.FileSystemObject");
Now I was able to create save and open functions which used the
Scripting.FileSystemObject to allow the XML to be passed back and forth between the Gadget and the Flyout.
Which brings us to permissions. When writing files the Gadget can write files to the file system, but only within the application path and subdirectories. If you try to manipulate files out side that path you'll get permission denied errors. Fortunatelythe Sidebar Gadget object model has a convenient property to give you access to the application path:
But because we're writing files to be passed back and forth we've created two problems for ourselves. First, the
System.Gadget.Settings object is instance independent, so if there are multiple instances of your control in the sidebar they don't conflict. But part of that benefit, is that you have no way of knowing how many instances are open or what their settings are. So if I'm writing files back and forth, I need to make sure I'm not conflicting with other instances of my Gadget.
Data.getTime method which returns the number of milliseconds since Jan 1, 1970. Since the probability of a user being able to add multiple instances of my Gadget to the Sidebar is nil this works fine for our needs. Then in order to allow the Flyout to retrieve the file all I need to do is pass the path to the file to
I want to backtrack for a moment to the Flyout events
onHide. My first choice was to use these two events to write and clean up the XML file. The
onShow seemed a good choice because it was the perfect trigger to tell my Gadget when the file was needed.
onHide because I found that the Gadget has no onClose event to allow me to clean up files when I'm done. Because the
System.Gadget.Settings object does not persist data after a Gadget is closed, if I don't delete the files then eventually the files will pile up in the users directory over time. Disk space may be cheap, but it's up to the user to determine how to use that space, not me.
As it turns out, the
onHide event worked fine for my needs. Unfortunately, I found out that
onShow fires after the
onLoad event of the Flyout. So now I needed to figure out how to write the file before the Flyout tried to access it in the
onLoad event. I finally decided to use the
onClick event to first, write the XML file to disk, then open the flyout. This guarantees that the file exists before the Flyout loads.
System.Gadget.Flyout.show = true;
So, as I mentioned before, when the user clicks on the name of a gallery the
onClick event fires and calls
loadFlyout and passes the id of the gallery as an argument. The
loadFlyout function writes the XML stored in a global variable to disk, then opens the Flyout.
The next issue I ran into was a surprise. I had been using an external CSS file for the general formatting of my page, and I wanted to set the background color of the Flyout. But it remained white. The page could view the CSS file, because I could change the height and width of the body, but other settings had no effect. But when I used inline styles, I had no problem. I searched around for a workaround, but only found other complaints of the problem with no solutions. At this point I abandoned the external CSS and used inline styles.
The last important feature of note was that I wanted a user to be able to go to my website and purchase a print of the image. I thought, there might be a problem with putting a link to an external resource on my flyout page, but there wasn't. In fact it was easier than I imagined. If you put a link to an external resource, a new IE window will open to the resource in the link - exactly the way I wanted it to happen!
Step 5: Packaging Your Gadget
So now, we're finished. Or almost. To deploy our newly completed Sidebar Gadget you have two options. You can simply copy the files to a new folder in the Gadget directory, then rename the folder to include ".gadget" at the end. So if our folder is named "Photogallery", just rename it to "Photogallery.gadget".
The above method may work fine if you are creating your own personal gadget and then just dropping it in the folder locally. But if you have a gadget like ours, and you want to deploy it to other users, this second method is actually easier. Simply create a zip file containing all the files and subfolders (but not the root folder itself), then change the .zip extension to .gadget. And that's it! When a user tries to download or open your compressed .gadget file Windows Vista will automatically install it to their user gadgets folder.
The Finished Product
There you have it, the complete package, from start to finish. How to develop a functional Vista Sidebar Gadget. I struggled a bit towards the end with a list of features I would have liked to implement or some changes I would make now that I better understand the process. But they'll have to wait until version 2.0. I'd be happy to hear your ideas for different features that could make this Gadget better. Here's a few that I'd like to add when I get time to work on this again:
- Resize the main Gadget window when the user undocks the Gadget.
- Resize the Flyout window when the user clicks a thumbnail in order to make the enlarged view even bigger.
- Add more user configurable settings like the location of the feed, the size of the flyout, the number of thumbnails to display at a time, as well as color and font settings.