Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Events Manager: A Vista Gadget

4.35/5 (28 votes)
2 Feb 20076 min read 1   2.6K  
An events manager gadget for Windows Vista
Sample image

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:

C#
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

JavaScript
System.Gadget.Flyout.file = "AddEvent.htm";
System.Gadget.Flyout.show = true;
// MainScript is used to call functions of this window from flyout
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

JavaScript
var radical = "Event" + eventId;
// save data using System.Gadget.Settings.write
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

JavaScript
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

JavaScript
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

JavaScript
function Init()
{
    ...
    // Set the settings html page
    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

JavaScript
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

JavaScript
function SetSettings()
{
    ...
    // EventsNumber
    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

JavaScript
function Search(keyword)
{
    ...
    // check if the event's name contain the keyword
    var offset = eventName.indexOf(keyword);
    // if yes :
    if(offset != -1)
    {
        // add the event to event's list and highlight the keyword
        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.

Image 2

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

JavaScript
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

JavaScript
// Update the gadget size
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

JavaScript
function SendMail(eventId)
{
    var radical = "Event" + eventId;
    var result = true;
    // create an XmlHttpRequest obj
    var xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
    // ServerSideScript : script used to send mail
    // method : POST
    // async : false
    xmlHttp.Open("POST",System.Gadget.Settings.read("ServerSideScript"),
                 false);
    xmlHttp.setRequestHeader("Content-Type",
                             "application/x-www-form-urlencoded");
    // Email data
    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 user specified an email adress
    if(email != "")
    {
        // send the request
        xmlHttp.send("address="+email+"&subject=notification : "+
                     name+"&message="+message);
        if(xmlHttp.status != 200)
        {
            // if request failed
            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
<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:

Debugger

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:

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:

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

JavaScript
function AddLineToEventsList(eventId)
{
    ...
    // play sound only if EnableSound == true
    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:

Scroll bar

I use a free control from Tigara. You can download it here.

Calendar

Calendars are really useful to easily pick up dates:

Calendar

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:

XML
<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

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