Introduction
Mac OS users can count on widgets since Mac OS X 10.4 (Tiger). Widgets are small applications that float on the user desktop and do a particular job. The most known are clock, calendar, weather, the converter of currencies,…
At its turn, Windows Vista features widgets where they are called "gadget". Gadgets are very simple and easy to create: you just have to be familiar with Html/JavaScript to develop your own gadgets. In this article, I share with you my gadget and details about how it works.
Ideally, to be successful, gadgets have to be useful. This is why I chose to create an events manager that can notify users of upcoming events by sending them an email before the event's date.
Events Manager
The major part of code that deals with event management is located in EventManager.js
and AddEvent.js
. The manager is responsible for adding events, deleting and displaying them. To better understand the way it works, it's important to know the structure of an event used by my gadget.
Event Structure
In C#, an event structure should look like this:
struct Event
{
int Id;
string Name;
string Description;
DateTime EventDate;
int notifyBefore;
bool SendEmail;
bool EmailSent;
}
Adding an event
To add an event, the user clicks on "Add Event". This link calls the function ShowAddEventFlyout
which display a flyout window (AddEvent.htm) :
Document.js
System.Gadget.Flyout.file = "AddEvent.htm";
System.Gadget.Flyout.show = true;
System.Gadget.Flyout.document.MainScript = this;
System.Gadget.Flyout.document.EventId = CurrentEventId;
System.Gadget.Flyout.document.EditEvent = false;
Once the user filled all the fields for the event, fields content is validated by ValidateData
. If data is valid, AddEvent
saves events data using System.Gadget.Settings.write
with a special format: "Event" + eventId + Property (see Event Structure)
:
AddEvent.js
var radical = "Event" + eventId;
System.Gadget.Settings.write(radical+"Id", eventId);
System.Gadget.Settings.write(radical+"Name", name);
System.Gadget.Settings.write(radical+"EventDate", date + " " + time);
System.Gadget.Settings.write(radical+"Description", description);
System.Gadget.Settings.write(radical+"NotifyBefore", parseInt(notifyBefore));
System.Gadget.Settings.write(radical+"SendEmail", SendEmail);
System.Gadget.Settings.write(radical+"EmailSent", false);
Deleting an event
With the gadget API, there is no official way to delete an entry written with System.Gadget.Settings.write
or System.Gadget.Settings.writeString
. But there is a trick. In Events Manager, RemoveEvent
is the function that "deletes" events:
EventManager.js
function RemoveEvent(eventId)
{
EventsNumber--;
System.Gadget.Settings.write("Event"+eventId+"Id", -1);
HideEventInfo();
SaveSettings();
LoadEvents();
UpdateSliderInput();
SetSlider();
}
As you see, we don't really delete the event, we only disable it. The Id
property is set to -1. Normally, Id
is equal to eventId
.
Display events
LoadEvents
is responsible for displaying all events. This function is often called to refresh the events in order to take any recent modification into account.
EventManager.js
function LoadEvents()
{
ClearEventsList();
for(i = 0, j=0; j<EventsNumber; i++)
{
if(System.Gadget.Settings.read("Event"+i+"Id")==i)
{
j++;
AddLineToEventsList(i);
}
}
}
The condition if(System.Gadget.Settings.read("Event"+i+"Id")==i)
avoid the for
loop to display deleted events. On the other hand, the function AddLineToEventsList
uses DOM to add events to the main html document (Document.htm).
Settings
While working on my gadget, I want it to be customizable. The sidebar lets gadgets specify an html file for settings. In the case of my gadget, the html page Settings.htm
and the script Settings.js
deal with the gadget's settings. Events Manager set the configuration file in the function Init
(the first function called):
Document.js
function Init()
{
...
System.Gadget.settingsUI = "Settings.htm";
...
}
With my gadget, you can set several parameters:
- Your email
- Events per page
- Enable sound
- Delete old events after x days
- Server-side script to send emails
GetDefaultSettings
When you run my gadget the first time, Events Manager has many default values. These values are hard-coded in the file Settings.js
and are accessible via the function GetDefaultSetting
:
Settings.js
function GetDefaultSetting(settingName)
{
switch(settingName)
{
case "EventsPerPage":
return 5;
case "CurrentEventId":
return 0;
case "EventsNumber":
return 0;
case "RefreshInterval":
return 5;
case "DeleteOldEvents":
return true;
case "OldEventDeletionDelay":
return 5;
case "YourEmail":
return "";
case "ServerSideScript":
return "http://membres.lycos.fr/mailrelay/mail.php";
case "EnableSound":
return true;
}
}
SetSettings
The function GetDefaultSetting
is used by SetSettings
to initialize global variables. SetSettings
check for each parameter if the user has specified a custom value. If yes, the value from the user is used instead of the default value. Otherwise, the default value is used.
Settings.js
function SetSettings()
{
...
if(System.Gadget.Settings.read("EventsNumber")=="")
EventsNumber = GetDefaultSetting("EventsNumber");
Else
EventsNumber = System.Gadget.Settings.read("EventsNumber");
...
}
Search Engine
Events Manager allow user to search events by entering a keyword in the search box. The function Search
is used for the search. Like LoadEvents
, Search
iterates through all the events and uses indexOf
to check if there is an event name that contains the keyword:
SearchEngine.cs
function Search(keyword)
{
...
var offset = eventName.indexOf(keyword);
if(offset != -1)
{
AddLineToEventsList(i, true, offset, offset + keyword.length);
}
...
}
Design
Appearence
There is an implicit rule in gadget world that says that gadgets have to be visually attractive. The problem is that developers are rarely good designers. In order to achieve a good design, I chose to use the Contacts gadget design as a base for my gadget's design.
Dock/Undock
With the gadget API, we can register to some important events to make our gadget more interactive gadget. Since we speak about design, two events retain our attention:
- System.Gadget.onDock
- System.Gadget.onUndock
We register to an event this way:
Document.js
function Init()
{
...
System.Gadget.onUndock = DockUndock;
System.Gadget.onDock = DockUndock;
...
}
DockUndock
updates the size of the gadget when it docks on the sidebar or when it undocks from the sidebar. System.Gadget.docked
is used to determine whether the gadget is docked or not:
Document.js
function DockUndock()
{
if (System.Gadget.docked)
{
HideEventInfo();
document.getElementById("Body").style.width = "134px";
}
else
{
document.getElementById("Body").style.width = "268px";
document.getElementById("LeftPage").style.visibility = "visible";
}
}
How to send email?
There is no function in the gadget API that sends emails. However, we can use the ActiveX object Microsoft.XMLHTTP
to send an HTTP request to a server-side script. The script will do the rest of the job.
Let's see the function responsible of sending email:
Notification.js
function SendMail(eventId)
{
var radical = "Event" + eventId;
var result = true;
var xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
xmlHttp.Open("POST",System.Gadget.Settings.read("ServerSideScript"),
false);
xmlHttp.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
var email = System.Gadget.Settings.read("YourEmail");
var name = System.Gadget.Settings.read(radical + "Name");
var description = System.Gadget.Settings.read(radical + "Description");
var date = System.Gadget.Settings.read(radical + "EventDate");
var message = "You have an upcoming event : " + name + ".\nDate : " +
date + "\nDescription :\n" + description +
"\n\nSent by Event Manager Gadget.";
if(email != "")
{
xmlHttp.send("address="+email+"&subject=notification : "+
name+"&message="+message);
if(xmlHttp.status != 200)
{
result = false;
}
}
else
{
result = false;
}
return result;
}
The server-side script url is retrieved via System.Gadget.Settings.read("ServerSideScript")
. The script should accept three parameters in POST mode: address (To)
, subject
and message
. For example, this is the code of a little PHP script compatible of my gadget:
mail.php
<html>
<head>
<title>Mail Relay</title>
</head>
<body>
<?php
$adresse = $_POST['address'];
$sujet = $_POST['subject'];
$message = $_POST['message'];
$message = wordwrap($message, 70);
mail($adresse, $sujet, $message);
?>
</body>
</html>
Debugging
Debugging a gadget is really easy. Indeed, it's as easy as debugging a script in Internet Explorer. First, runs IE and in the menu: Tools -> Internet Options -> Advanced -> uncheck Disable Script Debugging (Internet Explorer) and Disable Script Debugging (Other).
Now, if an error occurs, you can use Visual Studio (or other script debugger) to debug.
Usually, you will see a window that looks like this:
Deploy the gadget
To deploy your gadget, follow these steps:
- Compress all the files inside your gadget folder (zip or cab)
- Change the .zip or .cab extension to .gadget
- Double-click your .gadget file to install your gadget
It's easy but these steps can take a considerable time when you often deploy your gadget. This is why, to make deployment easier, I developed a little application called Deploy Gadget:
More information about deployment here.
Globalisation
With globalisation support, gadgets can be multilingual. You just have to create for each culture, a specific folder with the codename of the culture. It's not necessary to duplicate all the files, only those of the user interface. Events Manager supports two languages: French and English. This is my gadget's folder tree:
More details about globalisation here.
Complements and reference
Sound and music
In the gadget API, there are two functions that play sounds and music:
System.Sound.beep()
System.Sound.playSound(strPlaySoundInit)
The first one generates a simple beep. The second function plays sound file. Events Manager uses playSound to notify user of upcoming events:
EventManager.js
function AddLineToEventsList(eventId)
{
...
if(EnableSound) System.Sound.playSound("../sound/toutoune.wav");
...
}
Scrolling bar
When the number of events become superior to the number of events per page, my gadget display a scrollbar to allow users to see all the events in the list:
I use a free control from Tigara. You can download it here.
Calendar
Calendars are really useful to easily pick up dates:
You can download this nice control from Matt Kruse website. From the same author, I also used his date's management library.
Manifest file
Each gadget's have one manifest. This xml file contains information about your gadget like the author's name, copyright, gadget's name,… The data in you manifest file is used by the sidebar to load and display correctly your gadget. The manifest file is named "gadget.xml" (the name is not an option).
A typical manifest file looks like this:
<gadget>
<name>Name of the gadget</name>
<namespace>Example.You</namespace>
<version>1.0</version>
<author name=" Author name ">
<info url="http://www.example.com" />
<logo src="logo.png"/>
</author>
<copyright> Copyright </copyright>
<description> Gadget description </description>
<icons>
<icon height="48" width="48" src="icon.png" />
</icons>
<hosts>
<host name="sidebar">
<base type="HTML" src="YourMainPage.html" />
<permissions>full</permissions>
<platform minPlatformVersion="1.0" />
<defaultImage src="drag.png" />
</host>
</hosts>
</gadget>
You can find a list of all fields and a complete description for each of them here