Hi-Country Trips: a Trip Reservation System Using XDSP and Windows Sidebar Gadgets

By J. Jeffrey Hanson
February 28, 2008

AOL's Xdrive Data Service Platform (XDSP) is a web service API based on the JavaScript Object Notation (JSON) v1.2 API. Using XDSP, developers can create applications that access the features of Open Xdrive--applications that store data online, retrieve data online, and perform other data-related functions.

The Windows Sidebar is an optional panel introduced with the Windows Vista desktop that contains visually small applications known as gadgets, written using HTML, CSS, and/or scripting technologies.

In this article, I discuss how you can use the features provided by XDSP within a Windows Sidebar gadget to create a UI that gives users access to the features of XDSP right from their desktop, without launching a secondary application. Using this gadget, "guests" can join outdoor adventure trips, remove themselves from trips, and watch as their trip plans progress to completion, making necessary adjustments along the way.

Windows Sidebar Gadgets Overview

Windows Sidebar is a panel stationed on the side of the Windows Vista desktop where small--or mini--applications reside through which users can quickly access the application's functionality.

A Windows Sidebar gadget is an HTML-based application that can sit in the Windows Sidebar. Information and functionality contained within a gadget is constrained only by the capabilities of a typical HTML/HTTP client environment. Users can also "undock" a gadget from the Sidebar to float on their desktop.

Figure 1 illustrates a typical Windows Sidebar that contains two gadgets:

Figure 1. A typical Windows Sidebar

A gadget, in its distributed form, is a zipped file that has the extension .gadget. The .gadget file contains an XML-based configuration file known as a "manifest" that must be named "gadget.xml," one or more HTML/CSS/script files, and any other ancillary files and resources the application uses. A .gadget file can be installed by double-clicking, just as with an executable file.

All gadgets available to be placed in the Windows Sidebar can be selected from a Windows Vista palette called the Gadget Gallery. The Gadget Gallery presents all available gadgets, one page at a time, so a user can drag and drop the gadgets they want to the Windows Sidebar on their desktop.

Figure 2 illustrates a typical Gadget Gallery:

Figure 2. The Windows Sidebar Gadget Gallery

As shown in Figure 2, gadgets can be viewed a page at a time, or found using the search box at the top of the Gadget Gallery window.

Figure 3 illustrates a typical gadget:

Figure 3. A typical gadget

As Figure 3 illustrates, a gadget's main content is displayed in the main panel. Optional data can be viewed on a pop-out page known as a "flyout." Settings for a gadget are displayed and configured in a dialog box that is displayed when a user clicks the wrench icon on the right side of the gadget.

Xdrive Data Services Overview

JSON is a simple, textual data format that exploits the dynamic capabilities of specifying objects in JavaScript, while being easily understood by humans. The following example is a simple JSON object that defines one array with two items:

var aJSONObject = {
  "somestuff": [
    {
      "name": "item1",
      "somedata": "This is my data"
    },
    {
      "name": "item2",
      "somedata": "and this is my data"
    }
  ]
};

XDSP transmits and receives requests sent via HTTP as JSON objects. XDSP communicates within a web services gateway environment using JSON objects as the payload for message exchange.

The structure of an XDSP Xdrive API is built on a URL with the following structure:

http://plus.xdrive.com/json/version/method

In the URL, /json specifies the handler of all JSON-based messages for the Xdrive gateway; /version specifies the XDSP version; and /method is the specific XDSP method to call. Each method call takes an encoded JSON message being sent and returns an encoded JSON response. XDSP enables multiple, multipart HTTP requests to be chained together using a single HTTP POST.

The Hi-Country Trips Reservation System

To demonstrate XDSP, I will guide you through the basics of XDSP in the context of creating an imaginary company that puts together adventure trips. Guests can enroll on trips, remove themselves from trips, and monitor the status of trips using a Windows Sidebar gadget, right from their desktop.

The operating steps for the Hi-Country Trips are as follows:

  1. The user logs in from the Sidebar gadget to an Xdrive gateway that has been established to host trip reservations.
  2. The current active reservations are displayed in the gadget's main panel.
  3. The user can select to join or be removed from any of the active reservations.
  4. Details about each reservation can be viewed in the flyout panel of the gadget.
  5. When the user is finished, the user logs out from the Xdrive gateway within the gadget.

At this point, you should create an Xdrive account for yourself at http://www.xdrive.com/.

All communication with the Xdrive account will take place within the XDSP framework, facilitated by Asynchronous JavaScript and XML (Ajax) XML transport mechanisms.

Using XMLHttpRequest as the Transport Mechanism

XMLHttpRequest (XHR) is an API used by JavaScript and other HTML scripting languages to transfer XML and other data between an HTML client and a server. XHR can communicate in synchronously or asynchronously.

The data from XMLHttpRequest calls will often be returned as XML. However, it can be returned in any format you would like, as can XDSP when it is returned as serialized JSON objects.

In this example, you will use one JavaScript function for most of the communication duties for XDSP calls. This function, named ajaxPost, is defined as follows:

function ajaxPost(xdriveAPI, postData)
  {
    var xmlRequest = new XMLHttpRequest();
    xmlRequest.open("POST", xdriveAPI, false);  // false indicates synchronous call
    xmlRequest.send(postData);
    if (xmlRequest.status == 200) 
    {
      if (xmlRequest.responseText)
      {
        return xmlRequest.responseText;
      }
      else
      {
        return "";
      }
    }
    
    logDebug("ajaxPost failed with status: " + xmlRequest.status);
      
    return null;
  }

As you can see, the ajaxPost function takes an API method name and optional postData, makes a synchronous call to the XDSP gateway, and returns any response text to the caller.

Authenticating

The first order of business is to build the authentication framework for the reservation system. Login credentials are specified in the settings dialog box for the gadget. After the credentials are set, the member.login API for XDSP can be invoked.

The following code illustrates the details of constructing a call to the XDSP member.login API:

var tempSettings = new getGadgetSettings();
    
    if (tempSettings.username == null || tempSettings.username == "" ||
       tempSettings.password == null || tempSettings.password == "")
    {
      logError("Username and/or password empty. Open 'Settings' dialog to set.");
    }
    else
    {
      var reqObj = {
        'user' :
        {
          'type' : 'MemberObject',
          'username' : tempSettings.username,
          'password' : tempSettings.password
        }
      };
      
      var postData = "data=" + JSON.stringify(reqObj);
      var response = ajaxPost(https://plus.xdrive.com/json/v1.2/member.login?
                              + postData, "");
      if (response != null)
      {
        var jsonObject = eval('(' + response + ')');
        
        if (jsonObject && jsonObject.jsessionid)
        {
          // copy data to global object
          xDriveConfig.jsessionid    = jsonObject.jsessionid;
          xDriveConfig.recoveryToken = jsonObject.recoveryToken;
          xDriveConfig.username      = jsonObject.results.user.username;
          xDriveConfig.firstName     = jsonObject.results.user.firstname;
          xDriveConfig.lastName      = jsonObject.results.user.lastname;
          xDriveConfig.email         = jsonObject.results.user.email;
          xDriveConfig.rootFolderId  = jsonObject.results.user.rootFolderId;
          xDriveConfig.userSeq       = jsonObject.results.user.userSeq;
          
          setLoggedIn(true);

          document.getElementById("loginLogout").innerHTML =
            "<a href='javascript:callLogoutService();' "
            + "title='Logout from your Xdrive account'>Logout</a>";

          // call service again after every 60 seconds
          resIntervalID = setInterval('callGetReservationsService()', 60000);
          callGetReservationsService();
        }
      }
      else
      {
        logError("Login failed");
      }
    }

As shown here, when a successful login has been established, elements of the response are stored for later use in a global variable. Also, a timer interval is started that will check reservations every 60 seconds, and a call is made immediately to populate the list of active reservations.

The XDSP API allows cookie-less session support by returning the jsessionid and recoveryToken values in the response from a member.login call.

Logging Out of the Xdrive Account

When a session is complete, the user can log out of the Xdrive account using the member.logout API. Details of the logout process are shown here:

var response = ajaxPost("https://plus.xdrive.com/json/v1.2/member.logout", "");
    if (response != null) 
    {
      document.getElementById("loginLogout").innerHTML =
        "<a href='javascript:callLoginService();' "
        + "title='Login to your Xdrive account'>Login</a>";
      
      xDriveConfig.jsessionid    = null;
      xDriveConfig.recoveryToken = null;
      xDriveConfig.username      = null;
      xDriveConfig.firstName     = null;
      xDriveConfig.lastName      = null;
      xDriveConfig.email         = null;
      xDriveConfig.rootFolderId  = null;
      xDriveConfig.userSeq       = null;
      
      setLoggedIn(false);
      clearInterval(resIntervalID);
      resIntervalID = 0;
      document.getElementById("reservations_div").innerHTML = "";
    }    
    else
    {
      logDebug("Logout failed");
    }

Upon successful completion of the logout, variables are reset and the timer interval is cleared.

Setting Individual Guest Properties

To establish authentication credentials and personal information about a user, the user must open the settings dialog box for the gadget by clicking the wrench icon on the right side of the gadget.

When the Hi-Country Trips settings dialog box appears, the user will see a page similar to the illustration in Figure 4.

Figure 4. The settings dialog box for the Hi-Country Trips gadget

After the settings are established, the user clicks OK, and they are returned to the main panel of the gadget where they can proceed to log in.

When the user is logged in, all active reservations will be retrieved and their IDs will be displayed in the main panel of the gadget.

Retrieving All Reservations

The Xdrive method used to retrieve reservations is file.getlisting, which accepts a srcFile object in the following format:

    var reqObj = {
      "srcFile":
      {
        "type" : "FileObject",
        "id"   : xDriveConfig.rootFolderId
      }
    };

    var reqText = JSON.stringify(reqObj);
    
    var response = ajaxPost(https://plus.xdrive.com/json/v1.2/file.getlisting?
                            + reqText, "");
    if (response != null)
    {
      var resObject = JSON.parse(response);
      
      var children = findChildren(resObject);
      if (children)
      {
        var resDivHTML = "";
        
        for (var i = 0, n = children.length; i < n; i++)
        {
          if (typeof children[i] === "object")
          {
            if (children[i].isDir == false)
            {
              var subSection = children[i].filename.substring(12, 0);          
              if (subSection == "reservation-")
              {
                var resID = children[i].id;          
                var resName = children[i].filename.substring(12, 19);
                if (memberOfTrip(resID))
                {
                  resDivHTML += "<a href=\"javascript:callViewReservationService(\'"
                             + resID.substring(8, resID.length)
                             + "\');\" title=\"View details\">" + resName
                             + "</a>   "
                             + "<a href=\"javascript:disjoinTrip(\'"
                             + resID.substring(8, resID.length) + "\', \'" + resName
                             + "\');\" title=\""
                             + "Remove yourself from this trip\">x</a><br/>";
                }
                else
                {
                  resDivHTML += "<a href=\"javascript:callViewReservationService(\'"
                             + resID.substring(8, resID.length)
                             + "\');\" title=\"View details\">" + resName
                             + "</a>   "
                             + "<a href=\"javascript:joinTrip(\'"
                             + resID.substring(8, resID.length) + "\', \'" + resName
                             + "\');\" title=\"Join this trip\">+</a><br/>";
                }
              }
            }
          }
        }
        
        document.getElementById("reservations_div").innerHTML = resDivHTML;
      }
    }

As shown in the previous listing, the response from the file.getlisting method call is parsed to find the ID and name for each reservation. The response is also parsed to find whether or not the current user belongs to each individual reservation. HTML is constructed to allow the user to join a trip or be removed from a trip.

An example of the gadget showing active reservations is illustrated in Figure 5:

Figure 5. Active reservations displayed in the main panel of the Hi-Country Trips gadget

In the main panel of the gadget, each reservation ID is displayed, along with an x or a plus sign (+). The user clicks x to be removed from a trip, and clicks + to join a trip.

When the user clicks the ID for an individual reservation, the flyout page, showing the details for the reservation, will be displayed.

Retrieving Individual Reservations

Individual reservations are stored in the Xdrive gateway as separate files. To view an individual reservation, simply download the file corresponding to the reservation ID.

Xdrive supports the downloading of single files using the io.download method. Archived files can be downloaded using the io.zipdownload method. Because reservations are stored as plain text, the io.download method is used. Details of the download process are shown in the following code:

var reqObj = {
      "toDownload" :
      {
        "type" : "FileObject",
        "id" : "xdr:XFS-" + resID
      }
    };
    
    var reqText = "data=" + JSON.stringify(reqObj);
    
    var response = ajaxPost(https://plus.xdrive.com/json/v1.2/io.download?
                            + reqText, "");
    if (response != null)
    {
      var resObj = JSON.parse(response);
      
      var htmlStr = "";
      htmlStr = populateReservationHTML(resObj, htmlStr);
      
      showFlyout(htmlStr);
    }

It is worth mentioning at this point two JavaScript methods that are used in this article to handle JSON data. The JSON.stringify method takes a JSON object and returns a string that represents the serialized object. The JSON.parse method does just the opposite: it takes a serialized JSON string and creates a JSON object. Both methods are members of the JavaScript library found at http://www.json.org/json2.js.

When an individual reservation is downloaded, HTML is constructed to facilitate viewing each reservation from within the gadget's flyout page. The illustration in Figure 6 shows an example of the details for a reservation:

Figure 6. Reservation details displayed in the flyout page of the Hi-Country Trips gadget

As shown in Figure 6, each reservation has a type, the number of openings left in the trip, the start date, the end date, the price of the trip, and the guests currently signed up for the trip.

Adding a Guest to a Trip

If a user clicks the plus sign (+) next to an individual reservation, he or she will be added to the trip represented by the reservation. This process involves downloading the reservation to make sure it is current, adding the user's information to the guest list, and then uploading the file to the Xdrive gateway.

The following code illustrates the process for downloading the reservation:

var reqObj = {
      "toDownload" :
      {
        "type" : "FileObject",
        "id" : "xdr:XFS-" + resID
      }
    };
    
    var reqText = "data=" + JSON.stringify(reqObj);
      
    var response = ajaxPost(https://plus.xdrive.com/json/v1.2/io.download?
                           + reqText, "");
    if (response != null)
    {
      var resObj = JSON.parse(response);
      addToCurrentTrip(resObj, resID, resName);
    }

After the reservation is downloaded, the individual's data is added to the reservation guest list. As shown in the following code, this involves expanding the guest list array by one element and adding a new JSON object that contains the user's data to the array:

var tempSettings = new getGadgetSettings();
      
      if (tempSettings.username == null || tempSettings.username == "" ||
         tempSettings.age == null || tempSettings.age == "" ||
         tempSettings.gender == null || tempSettings.gender == "")
      {
        logError("Username, age, and/or gender empty. Open 'Settings' dialog to set.");
      }
      else
      {
        var idx = resObj.reservation.guests.length;
        resObj.reservation.guests.length = resObj.reservation.guests.length + 1;
        resObj.reservation.guests[idx] =
           JSON.parse("{\"id\":\"jefhanson9\",\"age\":51,\"gender\":\"m\"}");
        
        if (callUploadService(resObj, resName) == true)
        {
          document.getElementById("reservations_div").innerHTML = "";          
          callGetReservationsService();
          showFlyout("<b>You successfully joined trip: " + resName + "<b>");
        }
        else
        {
          logError("Failed to join trip: " + resName);
        }
      }

After the user's data has been added to the guest list, the file is then uploaded to the Xdrive gateway. The file data is uploaded as multipart form data using the io.formupload API as illustrated in the following code:

var reqObj = {
        'destFolder' :
        {
          'type' : 'FileObject',
          'id' : xDriveConfig.rootFolderId
        },
        'statusToken' :
        {
          'type' : 'StatusObject',
          'token' : '0.00908775924407874'
        }
      };

    	// build requestbody
    	var boundary = '------------7d72b92a170f1a';
    	var requestbody = boundary + '\r\n'; 
    	requestbody += 'Content-Disposition: form-data; name="data"' + '\r\n\r\n';
    	requestbody += JSON.stringify(reqObj) + '\r\n';
    	requestbody += boundary + '\r\n'; 
    	requestbody += 'Content-Disposition: form-data; name="files0";'
                   + ' filename="reservation-' + resName + '.txt"' + '\r\n';
    	requestbody += 'Content-Type: text/xml' + '\r\n';
    	requestbody += '\r\n';
    	requestbody += JSON.stringify(resObj);
    	requestbody += '\r\n';
    	requestbody += '\r\n';
    	requestbody += boundary + '--';

    	var xmlRequest = new XMLHttpRequest();
    	xmlRequest.open("POST", "https://plus.xdrive.com/json/v1.2/io.formupload", false);

    	// Set Headers
    	xmlRequest.setRequestHeader("Content-type",
                        "multipart/form-data; boundary=----------7d72b92a170f1a");
    	// Set Cookies
    	// this value is necessary as per
       // (bug: http://support.microsoft.com/?id=234486)
    	xmlRequest.setRequestHeader("Cookie", "any non-empty string here");
    	var cookie = "JSESSIONID=" + xDriveConfig.jsessionid;
    	xmlRequest.setRequestHeader("Cookie", cookie);

    	// Post Data
    	xmlRequest.send(requestbody);

    	if (xmlRequest.status == 200) 
    	{
        return true;
    	}
      else
      {
    	  logError('Upload file failed');
      }

The main difference between the preceding method call and other XDSP calls is the addition of request headers and cookies.

Removing a Guest from a Trip

If a user clicks the x next to an individual reservation he or she will be removed from the trip represented by the reservation. This process involves downloading the reservation to make sure it is current, removing the user's information from the guest list, and then uploading the file to the Xdrive gateway.

The following code illustrates the process for downloading the reservation:

    var reqObj = {
      "toDownload" :
      {
        "type" : "FileObject",
        "id" : "xdr:XFS-" + resID
      }
    };
    
    var reqText = "data=" + JSON.stringify(reqObj);
      
    var response = ajaxPost(L_API_DOWNLOAD + "?" + reqText, "");
    if (response != null)
    {
      var resObj = JSON.parse(response);
      removeFromCurrentTrip(resObj, resID, resName);
    }

After the reservation is downloaded, the individual's data is removed from the reservation guest list. As shown in the following code, this involves calling the JavaScript splice method on the guest list array to remove the element that contains the user's data from the array:

var tempSettings = new getGadgetSettings();
      
      if (tempSettings.username == null || tempSettings.username == "")
      {
        logError("Username is empty. Open 'Settings' dialog to set.");
      }
      else
      {
        for (var i = 0, n = resObj.reservation.guests.length; i < n; i++)
        {
          if (resObj.reservation.guests[i].id == tempSettings.username)
          {
            resObj.reservation.guests.splice(i, 1);
            break;
          }
        }
        
        if (callUploadService(resObj, resName) == true)
        {
          document.getElementById("reservations_div").innerHTML = "";          
          callGetReservationsService();
          showFlyout("<b>You were successfully removed from trip: " + resName + "<b>");
        }
        else
        {
          logError('Failed to remove you from trip: ' + resName);
        }
      }

Next, the file is uploaded to the Xdrive gateway the same way as before.

Summary

In this article, I demonstrate how to use the features provided by the XDSP API within a Windows Sidebar gadget to build a UI in which users can access the features of the XDSP API right from their desktop, without launching a secondary application. Combining the XDSP API with the Windows Sidebar gadget presents many more possibilities for creating useful, convenient desktop applications, or gadgets. Now, you have the tools to design and create your own.

Resources
: )

just excellent, thanks a lot!
Bst Rgds,
Michael B.