Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / containers / virtual-machine

Using a Vista Sidebar Gadget to Consume an Image Feed

3.33/5 (19 votes)
21 Jan 200724 min read 1   875  
The goal of this article will take you through the process of creating a Vista Sidebar Gadget for the first time
Sample image

Introduction

When I first installed Windows Vista on my Virtual Server I noticed the sidebar and thought, "Ok, that's pretty cool". But when I found out that all you needed was HTML and Javascript to create one I said, "WOW, that's awesome!". My mind filled with the possibilities. One of the first that came to mind when I saw the Feed Headlines gadget in the Gadget Gallery was an image feed. I figured if the gadget could pull an RSS feed, which is essentially XML, from a remote location and refresh the data on an interval then I figured I could pull custom XML and display images located on a remote server as well.

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
Image 2

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

What tools do you need to create a Sidebar Gadget? The short answer, "Notepad". Yup, if you are comfortable writing your HTML and Javascript or VBScript in a plain text editor then you can do just that. No compilation is needed. However, if you're planning on something more robust than your first "Hello, World!" gadget I'd recommend an IDE that supports script debugging. Because I do all my HTML in a text editor, I tried to get started with notepad on my first try. But since my 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.

Gotcha #1

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:

Globalization

If you want to make your Sidebar Gadget localized the key is to use declared constants for all your text. If you have images with text on them, you'll need to create additional copies of the image in each language you intend to support. Then create a folder with the language code (en-US for english united states, for example). That folder should contain a duplicate folder structure for the resources (js, css and image files) and the localized resources. In the image of my solution explorer above, you'll see I have a folder named "en-US" with a subfolder named js which contains a javascript file named local.js. Local.js contains all my declared constants for error messages and other text. If I wanted to support other languages I would simply duplicate the contents of en-US with values in the language being supported.

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.

XML
<?xml version="1.0" encoding="utf-8" ?> 
<gadget> 
    <name>MaxPreps Gallery Viewer</name> 
    <namespace>Developmentalmadness.Vista.Gadgets</namespace> 
    <version>1.0.0.0</version> 
    <author name="Mark J. Miller"> 
        <info url="http://developmentalmadness.blogspot.com"<BR>              text="Mark J. Miller's blog"/> 
    </author> 
    <copyright>© 2007</copyright> 
    <description>View high school sport's action photos!</description> 
    <icons> 
        <icon height="150" width="150" <BR>              src="images/icons/MaxPreps_Blk_130w.gif" /> 
    </icons> 
    <hosts> 
        <host name="sidebar"> 
            <base type="HTML" apiVersion="1.0.0" src="gadget.htm" /> 
            <permissions>Full</permissions> 
            <platform minPlatformVersion="0.3" /> 
            <defaultImage src="images/icons/MaxPreps_Blk_130w.gif" /> 
        </host> 
    </hosts> 
</gadget>             

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 name and icons are used to display an application icon above the name of your Sidebar Gadget in the Gadget Gallery. While version, author, info, copyright and description all are used by the details pane to describe your Sidebar Gadget. The info element has two attributes: url and text. 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.

Image 3

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.

Image 4

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 (HTML, HEAD and 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.

Image 5
HTML
<html xmlns="http://www.w3.org/1999/xhtml"> 
    <head> 
        <title> Settings</title> 
        <script type="text/javascript" src="js/settings.js"> </script> 
        <style type="text/css"> 
            body{
                width:200px;
                height:200px;
            }
        </style> 
    </head> 
    <body onload="loadSettings();"> 
        List size (#):<input type="text" id="maxCount" size="3" /> 
        <div id="errorMessage" <BR>             style="color:Red;font-size:12pt;font-family:Calibri;"> </div> 
    </body> 
</html>
Gotcha #2

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 type="text/javascript" src="js/settings.js" />. Unless you have a separate closing tag for your script reference, the Sidebar will not recognize it. So make sure your external SCRIPT tags use this format: <script type="text/javascript" src="js/settings.js"></script>

In order to use the settings page you'll need the following two lines of code which will be called from your main page:

JavaScript
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.

JavaScript
function loadSettings()
{
    //set the close event handler
    System.Gadget.onSettingsClosing = onClose;
    
    //read the current setting
    var sMaxCount = System.Gadget.Settings.read(SETTING_MAX_GALLERY_COUNT);
    
    //check to see if it has been set
    if(sMaxCount != "")
        gMaxCount = parseInt(sMaxCount);
        
    //set the value of the HTML control
    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:

JavaScript
function onClose(event)
{
    if(event.closeAction == event.Action.commit)
    {
        //make sure the value entered was numeric
        if(isNaN(maxCount.value))
        {
            //display error message
            errorMessage.innerHTML = "Please enter an integer value.";
            
            //cancel the 'Ok' action
            event.cancel = true;
            
            //exit function
            return;
        }
        
        //get the integer value
        gMaxCount = parseInt(maxCount.value);
        
        //save settings
        System.Gadget.Settings.write(SETTING_MAX_GALLERY_COUNT, gMaxCount);
        
        //indicate success
        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: commit or 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 write and 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:

JavaScript
var valueFromSettings = System.Gadget.Settings.read("myValue");
var myBool = !!valueFromSettings;

Now lest move on to the actual Gadget functionality

Step 3: The Gadget UI

Image 6

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:

gadget.js

JavaScript
///////////////////////////////////////////////
//  loadMain()
//
//  Summary: called by body onload event from 
//      gadget.htm. Sets Gadget file references
//      and loads XML data
///////////////////////////////////////////////
function loadMain()
{
    // ... other init code here ....
            
    //get settings
    var sMaxCount = System.Gadget.Settings.read(SETTING_MAX_GALLERY_COUNT);
    if( sMaxCount != "" )
        gListMax = parseInt(sMaxCount);
        
  //set flag to resize the height of the
  //gadget after building list
    gResizeGadget = true;
    
    //retrieve data
    makeXmlRequest(DATA_RESOURCE_URI, AJAX_TYPE_GALLERIES);
}

ajax.js

JavaScript
/////////////////////////////////////////////////////////////////
//  getXmlContent()
//
//  Summary: called by the onreadystatechanged event, if
//      the request is complete it gets the XML document and 
//      saves it to disk so that it can be accessed by all
//      gadget pages. Then it calls printGalleries() to 
//      update the list of galleries on the gadget.
//////////////////////////////////////////////////////////////////
function getXmlContent()
{
    //check to see if the request is complete
    if(checkReadyState(gRequest))
    {
        //get XML doc
        var ajaxDoc = gRequest.responseXML;
        
    //store XML in global variable
      gXmlDoc = ajaxDoc;
       
        //if this is a galleries xml doc refresh the gadget
        if(gAjaxType == AJAX_TYPE_GALLERIES)
            printGalleries();
            
        //set timer to refresh data again in 5 minutes
        setTimeout("refreshData()", 5 * 60000);
    }
}

/////////////////////////////////////////////////////////
//  refreshData()
//
//  Summary: downloads data resource uri and refreshes 
//      xml data. If xml data is currently being read
//      the process is rescheduled for 1 min later
/////////////////////////////////////////////////////////
function refreshData()
{
    //make sure the data isn't currently being read
    if(gReadingXml)
    {
        //reschedule data refresh
        setTimeout("refreshData()", 1 * 60000);
        return;
    }
    
    //add 'random' parameter to prevent caching
    var dt = new Date();
    
    //resubmit AJAX request
    makeXmlRequest(DATA_RESOURCE_URI + "?t=" + dt.getTime(), <BR>                   AJAX_TYPE_GALLERIES);
}

To tie the missing stuff together here, onLoad calls 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 refreshData.

Layout

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.

JavaScript
//create a row and a cell to display the data
var newRow = table.insertRow(table.rows.length);    
var cell = newRow.insertCell(0);

//build the innerHTML string
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> ";

//set the innerHTML for the table cell
cell.innerHTML = html;

//format the title so that long text gets cut of with an ellipsis
eval("item" + i).style.textOverflow = "ellipsis";
eval("item" + i).style.overflow = "hidden";
eval("item" + i).style.whiteSpace = "nowrap";
eval("item" + i).style.width = 115;

//format the sport so that long text gets cut of with an ellipsis
eval("sport" + i).style.textOverflow = "ellipsis";
eval("sport" + i).style.overflow = "hidden";
eval("sport" + i).style.whiteSpace = "nowrap";
eval("sport" + i).style.width = 115;

//place a border between items to make it easier to read
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 textOverflow, overflow and 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

Image 7

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:

JavaScript
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 onShow and 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 onShow and 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.

Gotcha #3

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 Scripting.FileSystemObject.

ajax.js

JavaScript
///////////////////////////////////////////////////
//  saveXmlDoc(xmlDoc)
//  Parameters: xmlDoc - XmlDocment object to be saved
//      to local disk
//  Summary: saves the specified XmlDocment object to
//      the local disk to make it available to flyout.htm
///////////////////////////////////////////////////
function saveXmlDoc(xmlDoc)
{
    //set flag so data won't get overwritten while we're using it
    gReadingXml = true;
    
    //get storage path
    var path = getDataPath();
    
    //create/open text file
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    var output = fso.OpenTextFile(path,2,true);
    
    //write XML data to file
    output.WriteLine(xmlDoc.xml);
    
    //close file
    output.Close();
    
    //reset flag
    gReadingXml = false;
}

/////////////////////////////////////////////////////////
//  deleteXmlDoc(path)
//  Parameters: path - path of the XML file to delete
//  Summary: deletes specified file from local disk.
/////////////////////////////////////////////////////////
function deleteXmlDoc(path)
{
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    fso.DeleteFile(path);
}

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: System.Gadget.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.

I decided the way around this was to use a file name that would be unique among all instances of my Gadget. And the easiest method available was the javascript 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 System.Gadget.Settings.

Event Sequence

I want to backtrack for a moment to the Flyout events onShow and 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.

gadget.js

JavaScript
///////////////////////////////////////////////
//  loadFlyout(galleryId)
//  Parameters: galleryId - (string) the id value
//      of the selected GALLERY element
//  Summary: Called from onclick event of gadget.htm.
//      It stores settings needed by flyout
//      to display selected gallery and opens
//      the flyout
///////////////////////////////////////////////
function loadFlyout(galleryId)
{
    //write the xml to local disk so it can be accessed by flyout.htm
    storeXml();
    
    //display the flyout
        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.

Gotcha #4

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.

External Links

Image 8

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here