|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionWhen 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:
"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:
A Gadget application may also include the following items:
Gadgets have two installation directories:
Gadget Development Tools & ResourcesWhat 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 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:
GlobalizationIf 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 FileThe 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 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"
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
Because of the lack of documentation, I'm guessing on some of this, but most of what is below the Step 2: The Settings PageAs 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 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 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"
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 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 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 The first is straightforward, just create a function with the following signature and pass it to the onSettingsClosing delegate: 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 In this case, I check to see if the user clicked the "OK" button ( When you want to persist your settings you have two methods: This brings us to the second step I mentioned: reading the current settings. The 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: gadget.js///////////////////////////////////////////////
// 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/////////////////////////////////////////////////////////////////
// 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(),
To tie the missing stuff together here, LayoutA 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 //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");
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 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 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 The best way to communicate with the flyout is to use the 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 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 ajax.js///////////////////////////////////////////////////
// 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 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 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 Event SequenceI want to backtrack for a moment to the Flyout events As it turns out, the gadget.js///////////////////////////////////////////////
// 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 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 LinksThe 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 GadgetSo 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 ProductThere 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:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||