This article was written and submitted by an external developer. The Google Desktop Team thanks Teodor Filimon for his time and expertise.
"The beacon! The beacon of Amon Din is lit." - The Return Of The King
These days, we use IM clients, mail readers, and feed fetchers on our desktops. What if we could combine all this functionality (and more) into our own good-looking, customizable mini-apps? Google Desktop gadgets are perfect for the job, and in this article, we'll examine various ways to integrate with the online world. We'll also discuss how to tighten security and improve the efficiency of your online enabled gadgets.
First of all, we have to realize that gadgets are capable of doing a lot of things while online, such as downloading data or interacting with other users of the same gadget. However, if the machine is offline, your gadget should detect this and behave properly. More about offline and online detection can be found in the Offline Detection and Handling and Proper Offline Behaviour articles.
First of all, we'll address a fairly ubiquitous resource:
the World Wide Web. Our gadgets can fetch all kinds of stuff from it: news,
images, videos, etc., using the XMLHttpRequest
object. When dealing with news feeds, most of the time we'll be working
with XML. That's why knowing how to parse XML files can be helpful.
The XMLHttpRequest
object usually caches what it fetches, and many times you need to prevent the usage of old data. There are many techniques to bypass the cache, but I'd like to suggest adding a random parameter value to the URL.
For example, if you want to fetch a file at http://teodorfilimon.com/xmlUrl
, you can append a parameter like this: "http://teodorfilimon.com/xmlUrl?r=" + Math.random();
.
Hopefully, the most valuable resources in our lives are our own friends. Therefore, a truly interactive gadget should also involve friends, which are accessible through the googleTalk
object. Multi-user gadgets such as these require two actions: sending and receiving data. There is an example of this later on.
In the Librarian gadget, there is a main list of friends and the books they are reading at the moment. Although it is tailored to the specific needs of book fans, it uses techniques that can be applied to other gadgets:
There are many ways to represent a list in Desktop gadgets, however we need to keep in mind that the underlying data can change frequently in an online gadget. We could use a traditional list box (in UI terms), which can be implemented with the listbox control (Figure 1). The listbox shows a lot of information in a small space and has a well-defined API for changing its items. A detailed explanation of its multiple uses and features can be found in Yannick's Listboxes and Scrollbars in Google Desktop Gadgets article.
When space is limited, you may instead display a single item at a time and implement a bidirectional navigation (likely using left/right arrows). You should also find a way to inform the user about incoming updates. One way is to use content items with the appropriate notification flags. Needless to say, you should avoid using alerts as users could find that annoying. :)
Most lists will be refreshed or updated eventually, especially those that deal with online data. We need to avoid wasting CPU and network resources by refreshing too frequently. From my experience, 15-25 seconds is a good interval, though your specific situation may be different.
To declare a periodic event, you can use the view.setInterval
function.
In the Librarian gadget, the refresh logic looks like this:
function refreshFriends(){ if (friends!=null) reset(); friends=googleTalk.friends.toArray(); _addAllItems(); } // setting the interval is easy. the second parameter is the interval in milliseconds. timer=setInterval("refreshFriends();",20000); //terminating the timer can be done like this: function stopRefreshing(){ clearInterval(timer); }
Once we receive new data, how do we dynamically add items to the list? 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 to the list for every entry in the friends array. Also notice how I first check to see if Google Talk is working and display an appropriate message if it's not available. Here is the append
function, which adds the items to the listbox:
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){ debug.trace('googleTalk.sendTalkDataEx threw an exception'); } 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; }
We use the same XML string to construct the listitem as what you would use in a view.xml file. I'm also color coding the status of each friend, just like in Google Talk.
Every time I add one of my friends to the list, I need to "ask" his gadget what books he is reading. In the above code, this is done using the sendTalkDataEx
method.
It is called inside a try-catch clause because the friend might have gone offline, and attempting to send to an offline friend throws an exception. :D
Now I'll show you an example of how to receive data. This example shows how a gadget listens for a query from a friend's gadget. In the gadget's protocol, this is indicated by the token "iwanttoknowwhatyourereading".
//setting the hanler googleTalk.onReceiveTalkData=onReceiveData; function onReceiveData(friend,data){ if (data=="iwanttoknowwhatyourereading"){ //... } else { //the answer to the previous question //... } }
Communication can be vastly more complex than the protocol used above. You have to figure out what would be best for your gadget and handle everything accordingly.
As we've seen, refreshing data is the core of online enabled gadgets. But the data isn't the only thing subject to change, as the gadget itself can change state locally. For example, the Librarian gadget performs different actions at any given time, and the user needs to be informed about what's happening.
To be even more specific, think about fetching news from an RSS or ATOM feed. If users don't see any progress indication, they're likely to uninstall the gadget. A simple rotating image can do the trick — it's an efficient way to indicate to the user that something is happening in the background.
Sometimes the fetched data is very large, especially when dealing with a big news source or database. This means that it can take a long while before the data is completely refreshed, so we need to be able to use the old data in the meantime. Therefore we need to store everything between sessions using options. The first thought might be to create an options entry for every individual array item, but that wouldn't be space efficient. Using JSON, we could instead serialize the data array into a string representation and unpack it later on.
Once the gadget reaches a certain level of complexity, hidden "traps" can appear. For example, if your gadget uses Google Talk communication both in the main view and options dialog, we need to update the receive handler depending on what view is current. One solution is to change an options value within the options exit event. We then intercept that change in the main view and swap in the correct handler:
onoptionchanged="googleTalk.onReceiveTalkData=onReceiveData;"
This is another thing we have to be concerned with in online enabled gadgets. Consider the eval()
function which evaluates any script it receives as a parameter. You shouldn't use it to evaluate incoming data, since a user could modify a gadget and generate arbritrary scripts which are evaluated on other gadgets. Please read this section of the googleTalk object documentation to learn more.
Proper online behavior isn't only about tips and tricks, but also about finding an equilibrium with the communication resource and playing well 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!
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.
This work is licensed under a
Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License.