This software article series can be applied to gadget/widget programming, and is exemplified on Google Desktop gadgets.


Proper Online Behaviour

Teodor Filimon, Gadget Developer
December 2007
"The beacon! The beacon of Amon Din is lit."  -  The Return Of The King

Introduction

These days we use IM clients, mail readers and feed fetchers all the time. But what if we could combine all this functionality (and more) into our own good-looking, customizable mini-apps, there when we need them, doing all the work for us? Google Desktop gadgets are perfect for the job, and in this article we'll discover some ways to integrate users in the online world, keeping in mind security and efficiency.

Online Detection

First of all, we have to realize that gadgets can do a lot of things while online, like fetching news, feeds or images and interacting with other users of the same gadget. So, depending on what we use the gadget for, there are several ways to see if the computer is online or offline:

Communicating with the Online World ...

... through XMLHttpRequest

First of all, we have to see how we can use the most spread resource out there: the World Wide Web. Our gadgets can fetch all kinds of stuff from it: news, images and more, and it's able to do this using the XMLHttpRequest object. When dealing with news, most of the time we'll handle items organized in an XML format. That's why knowing how to parse XML files can be helpful. Going even further, we can also interact with online databases (e.g. through an ASP LINK file which accesses a database and returns the data in an XML format).

We can't forget about a common issue: caching. The XMLHttpRequest usually caches what it fetched, so you have to prevent the usage of old data. Ways to disable or bypass caching are pretty diverse, but adding a random parameter to the URL string has been widely used. So, for example, if you want to fetch a file at xmlUrl, you just append that parameter like this: xmlUrl+="?r="+Math.random();

... through Google Talk

However, probably the most relevant resource to us are our own friends - we communicate with them, add them in Google Talk and see what they're doing all the time. So a truly interactive feature should also involve friends, which are accessible through the googleTalk object. Basically, to achieve interaction we need to implement two actions: sending and receiving. I also included an example of how to do this next.

Example

Using Google Talk to compile your own list of friends
Figure 1: Live friends list in the Librarian gadget

In the Librarian gadget i made a list of friends which displays what everyone's reading at a certain moment. Although i tailored it to the specific needs of book fans, you'll notice that it uses techniques which can be applied in any gadget:

Representing a list

There are many ways to do this. However, we need to keep in mind that data changes all the time, so in order to increase the visibility of these changes we should use a "tall" list, which can be obtained through the listbox control (you can see one in Figure 1). A detailed explanation of its multiple uses and features can be found in Yannick's article.

Of course, this isn't always possible when you don't have much space available, and a solution for this is another common practice among developers: displaying a single friend/item and implementing bidirectional navigation in the list (simple increasing or decreasing of the array index). Though using this is obviously more space efficient, you should also find a way to inform the user about the latest changes. Preferably this should be done non-intrusively (e.g. using content items with appropriate notification flags) - try to avoid alerts since they require user action every time they occur.

Refreshing

Every list needs it. As i've said, especially the ones that deal with online data, but we also need to be careful not to use CPU resources inefficiently. Usually, 15-25 seconds provide the necessary equilibrium, but only you can decide, based on your specific situation. To 'declare' a repetitive action, like refreshing, you can use the view's setInterval method. In my case, the refresh function looked like this:

function refreshFriends(){
	if (friends!=null) reset();
	friends=googleTalk.friends.toArray();
	_addAllItems();
}

//setting the interval is easy:
timer=setInterval("refreshFriends();",20000);

//terminating the timer can be done like this:
function stopRefreshing(){
	clearInterval(timer);
}

As you can see, the most important things are getting the latest friends list and materializing it in the gadget.

Adding items

How do we actually to this? You can see how i did it below:

function _addAllItems(){
	noGTALK.visible=(googleTalk.talk_status!=2);
	var item=new Object();
	var aux=friends.length; //start from 0 again
	for (var i=0;i<aux;i++) append(friends[i],i);
}

The _addAllItems function adds an item for every entry in the friends array. Also notice how i first check Google Talk's status, displaying or hiding a label with an appropriate message. Here is the explanation for the append function:

function append(friend,i){

	var statustext="";var statuscolor="";

	switch(friend.status){
		case 0:statustext="available";statuscolor="#006400";break;
		case 1:statustext="idle";statuscolor="#FF8C00";break;
		case 2:statustext="busy";statuscolor="#FF0000";break;
	}

	try{
		googleTalk.sendTalkDataEx(friend.user_id,"iwanttoknowwhatyourereading",1);
	}catch(error){
	}

	var xmlToAppend="<listitem name='ITEM_"+i+"' tooltip='&clickForMore;' onclick='if (confirm(strings.doesntHaveGadget)) if (prompt(strings.invitationLabel,strings.invitationMessage)) {googleTalk.sendTalkText(friends["+i+"].user_id,strings.invitationMessage);alert(strings.messageSent);}'><label x='20' width='200' valign='middle' size='9' tooltip='&clickForMore;'>"+friend.name+"</label><label color='"+statuscolor+"' size='7' x='22' y='13' width='80' tooltip='&clickForMore;'>"+statustext+"</label><img y='3' src='nodata.gif' /></listitem>";

	list.appendElement(xmlToAppend);

	//updating the height and visibility of the scrollbar
	_sbOnChange();
	if (friends.length>=6)
		sb.visible=true; //no point for a scrollbar if there aren't many items
	else
		list.y=0;
}

This is the function used to add every item in my list. The XML string of the listitem to add is the same as what you would write in your .xml file, the difference is that here you append it dynamically. As you can see, i'm also using this "occasion" to color code the status of each friend, just like in Google Talk.

Sending data

Every time i visually add a friend to my list i need to "ask" his gadget what he's reading, which is done using the sendTalkDataEx method. It is inside a try-catch clause because, although errors aren't likely to appear, the friend you're sending a message to might have already gone offline by the time you get to send it. :D Although in this case it's pretty interesting, the try-catch clause has good parts, as well as bad. For example, it's often best to figure out every possible error and treat it in the code through various techniques, than just preventing the user of getting an error message.

Receiving data

Gadgets can "discuss" many different things, but even in the same discussion answers could be different. This happens because the gadget actually talks to itself, as an instance from another machine. :) So where i handle the receiving of a query (represented by the "iwanttoknowwhatyourereading" string in my case), i also have to handle the answer:

//setting the hanler
googleTalk.onReceiveTalkData=onReceiveData;

function onReceiveData(friend,data){
	if (data=="iwanttoknowwhatyourereading"){
		//...
	} else { //the answer to the previous question
		//...
	}
}

Communication can be more complex, or simpler than a question-answer schema; you have to figure out what would be best for your gadget and handle everything accordingly.

Data Handling

As we've seen, refreshing data used by the gadget is important, but what our friends are doing isn't the only subject to change. For example, our own gadget performs different actions at different times, and the user needs to be informed on what's happening so that he can have the right expectations.

To be even more specific, think about fetching news from an RSS/Atom feed. If users don't see any message, or motion, they're likely to uninstall because it would seem the gadget isn't doing anything, when in fact it hasn't finished fetching the data yet. A simple rotating image can do the trick, because it's suggesting a process is taking its course and it's also very efficient resource-wise.

But not every contact with a gadget is the first, and sometimes fetched data is very large, especially when dealing with a big news source or database. This means that it can take a while before the data is refreshed, so we need to be able to use the old data in the mean time. Therefore we need to store everything between sessions, using options. The first thought might be to make an entry for every array item, but that wouldn't be very efficient (e.g. explicit storage of hierarchies, array lengths, etc.). That's why we should serialize an array into a string representation when it's updated, and unpack the string into the initial object again if new data isn't available, using JSON.

Safeguards and Traps

Complexity

Once the gadget reaches a certain level, new kinds of traps can appear. For example, if your gadget uses Google Talk communication both in the main view and options dialog, changing focus between them can be a problem because we need to update the receive handler in the current view. A solution can be changing an option on an exit event from the options view (Ok or Cancel), and intercepting that change in the main view by adding this property to it:

onoptionchanged="googleTalk.onReceiveTalkData=onReceiveData;"

Security

This is another thing we have to keep in mind where interaction is concerned. For instance, consider the eval() function which evaluates any script it receives as a parameter. You can't use it to evaluate received expressions because a user could modify a gadget and generate unexpected behavior in the ones it communicates with; that's why eval() calls are checked to make sure they are not evaluating such data.

We've already talked about refreshing information, but no matter how we decide to get it or send it from and to other locations, we need to know the impact on the machine's resources, which we'll discuss in the next section.

Usage of Resources

CPU and memory

CPU and memory are the two main resources a computer can allocate to its software. We've seen how refreshing often is bad for the CPU and refreshing rarely is bad for data accuracy. That's why moderate intervals can sometimes be the best solution. Memory is a more general concern, because we're always thinking about compressing images, creating good-looking interfaces with few and efficient UI elements, etc., and this should also be true when considering proper online behavior.

Bandwidth

Or better-said, the internet connection. Referring to it as a connection rather than bandwidth can be obvious because a gadget doesn't use the Internet aggressively like P2P sharing programs or web browsers. However, timing and structure are of a bigger importance because sometimes they can determine incorrect behavior if they aren't planned thoroughly. For example, think about a chess game. You ask your opponent what color he wants to play with, but before he can answer, your best friend just comes online and you start a game with him. However, you get the answer which the previous opponent had to choose before proceeding, and the gadget thinks it refers to the current game, behaving in a wrong way. Such situations can easily be avoided, but it's important to know when they might occur so that we can take the necessary measures.

Conclusion

Proper online behavior isn't only about tips and tricks but also about a certain equilibrium your gadget has to achieve with the communication environment and with instances of the same gadget on other machines. As the programmer, you have to realize where this equilibrium is and how it can be achieved.

Many gadgets, everyone!

Resources


Author Bio

Teodor Filimon

I'm a natural born programmer. My first contact with a techno-gadget was Star Trek (remember those cool sensors?). I used to fill up a whole room with drawings of them when I was only 3 years old. Generally, I find a lot of inspiration for intuitive interfaces in things with "star" in their names (like Star Wars, Stargate,... :-) . I like the border between interface and function—it's the essence of a program, I think. Anyway, I'm a software engineering student now, and you can learn more about me and what I'm thinking and doing at my website or blog. My most popular gadgets are DigiWatch and TV Set.