My favorites | English | Sign in

Google Desktop APIs (Labs)

Intuitive User Interfaces - Tips & Tricks

This article was written and submitted by an external developer. The Google Desktop Team thanks Teodor Filimon for his time and expertise.


Teodor Filimon, Gadget Developer
June 2007
When someone asks you, "How did you do that?" you know you've done something right.  :)


Introduction

Lately, the boundary between users and developers has been getting thinner as newer programming interfaces and platforms make creating applications progressively easier (and more fun!). But even today, some seemingly simple things can still trip up a developer. Thus, to code efficiently and avoid errors, you still need to constantly learn and practice.

This article is a collection of tips and tricks that I've discovered during my hacking with the Google Desktop Gadget API. I hope you find these tips helpful in writing your own Desktop Gadgets. Links to source code of all the examples discussed here can be found at the end of this article.

Lots of UI Elements != a Big main.xml

If your gadget is graphically intensive—for example, a visual simulation of frequently changing data—you'll probably need a lot of images. Also, since the data is dynamic, these images are likely rapidly appearing and disappearing. How should you cope with that?

In one gadget, I needed to plot 30 points in order to generate a nice-looking wave signal (Figure 1). To achieve this, I could have created 30 <img> objects.

XML Elements
Figure 1: Drawing a wave

<img name ="point1" src="point.png" />
<img name ="point2" src="point.png" />
...
<img name ="point30" src="point.png" />

However, there is a much more efficient way to do this; you can generate the elements dynamically using code:

for (i=1;i<=30;i++)
  view.appendElement("<img name='point"+i+"' src='point.png' />");

The appendElement method allows for more cool tricks:

  • What if I had some extra parameters for each point? For example, we might need to set an initial position differently for each img.
    for (i=1;i<=30;i++)
      view.appendElement("<img name='point"+i+"' src='point.png' x='50+"+(5*i)+"' y='"+(i%2==0)?"1":"-1"+"'>
    
    // The (i%2==0)?"1":"-1" part just returns the character "1" 
    // if the condition is true (i is even) and "-1" if it's false.
  • How can I refer to the elements in my JavaScript code? appendElement returns a reference to the element that was created. Just store the references in an array and you can de-reference and use the elements however you want later.
    var points=new Array();
    for (i=1;i<=30;i++)
      points[i]=view.appendElement("<img name='point"+i+"' src='point.png' x='50' y='"+(i%2==0)?"1":"-1"+"'>/");

    Now you can read or manipulate any element in array points, for example, alert(points[3].y);

Other useful related methods include insertElement, which allows you to choose the position where an element is inserted, and removeElement. Also, all these methods are supported in the <div> object.

User Actions Translated into Accessible Objects

You have a lot of events at your disposal in the Google Desktop Gadget API. Some of the mouse-event handlers include

  • onclick
  • onmouseover
  • onmouseout
For keyboard events, you have
  • onkeypress
  • onkeydown
  • onkeyup
...and more.

An event handler can indicate when a user takes action, but, what if you want more details about what the user actually did? (Such as, which key did she press? Or, which object did she click on?)

The event object is there precisely to give us such information. It doesn't seem very important at first glance, but it's a tool which enables a gadget to discover more information about the event. Here are a couple of instances where I found the event object extremely useful.

Have a look at how I made the "Run" box for the gadget in Figure 2. It basically involved assigning a function to the onkeypress event of the <edit> object and then using the data provided by event:

Search box example
Figure 2: Capturing events
//in main.js

function KeyPressed(){
 if (event.keyCode==13) ReturnKey()
}

function ReturnKey(){
 if (runBox.value=="")
   runBox.value="Type here to run";
 else
   o.Run(runBox.value)
}
//in main.xml

<edit
  name="runBox" x="44" y="198" width="108" height="13"
  font="Arial" size="7" value="Type here to run" onkeypress="KeyPressed();"
/>
I am able to capture when a Return key is pressed by checking event.keyCode.

As I'm writing this article, I'm also working on a gadget which lets you drag objects over a map. All I needed to do was assign the function below to a div's onmousemove event.

function UpdateMapUI(){
  if (ActiveBall!=null) {
    ActiveBall.x=event.x;
    ActiveBall.y=event.y;
  }
}
event.x and event.y return the mouse pointer's x,y-coordinates.

I also found event.button and event.srcElement very useful. The first indicates which mouse button has been pressed, and the second returns the actual element on which the event was triggered. So you can have code such as alert(event.srcElement.x+' '+event.srcElement.y) to find any element's position. The event object is definitely worth taking a look at.

Saving Space When Adding Features

Menu example
Figure 3: Creating menu items

Space can become a problem if you keep adding features, especially if they are essential to your gadget. A good way to deal with this issue (besides using different faces depending on whether the gadget is in the sidebar or not) is a menu as demonstrated by the Ticker for Trekkers gadget.

Here's a snippet of the code used to generate the menu. Some of the objects and variables in the snippet are defined elsewhere, but the focus here is on the types of menu items you can create:

// Custom plugin context menu
pluginHelper.onAddCustomMenuItems = MAIN_menuAddCustomItems;

...

/*
 * Handler that generates the gadget's context menu.
 *
 * @param {Object} menu Menu object provided by Google Desktop
 */
function MAIN_menuAddCustomItems(menu) {
  var menuDateType = menu.AddPopup(strings.MENU_DATE_TYPE);
  var sd_type = options.GetValue('sd_type');
  for(var i = 0; i < STARDATE_TRAITS.length; i++) {
    menuDateType.AddItem(STARDATE_TRAITS[i].name,
        (sd_type == i) ? gddMenuItemFlagChecked : 0,
        // Force closure to reference a copy of the _current_ i
        MAIN_createOptionSetter('sd_type', i));
  }
  menu.AddItem('', 0x800 /* MF_SEPARATOR */, null);
  menu.AddItem(strings.MENU_WITH_ISSUE,
      options.GetValue('sd_with_issue') ? gddMenuItemFlagChecked : 0,
      MAIN_createOptionSetter('sd_with_issue',
        !options.GetValue('sd_with_issue')));
  menu.AddItem(strings.MENU_WITH_TIME,
      options.GetValue('sd_with_time') ? gddMenuItemFlagChecked : 0,
      MAIN_createOptionSetter('sd_with_time',
        !options.GetValue('sd_with_time')));
  menu.AddItem('', 0x800 /* MF_SEPARATOR */, null);
  menu.AddItem(strings.MENU_GOOGLE_SEARCH, 0, function () {
        new ActiveXObject('WScript.Shell').
          Run('http://www.google.com/intl/xx-klingon/');
      } );
}

You can create normal menu items with menu.AddItem(...), checked items in conjunction with gddMenuItemFlagChecked, disabled items in conjunction with gddMenuItemFlagGrayed, and submenus with menu.AddPopup(...).

Another way to keep the UI uncluttered is to have multiple display states. For example, a gadget may have two modes: a normal display and an "edit settings" display. In order to know when to switch between the two modes, we can make use of event handlers such as onmouseover and onmouseout to determine the user's intentions.

Sensitive inteface
Figure 4: DigiWatch's two display modes

You can see that my DigiWatch gadget normally shows time and date. That's the primary display mode (first image). But when the mouse hovers over the gadget (second image), the display changes: the date disappears and several control elements appear (set time, set alarm, 12/24h, and so on). This functionality has the same aim as tooltips: to optimize information presentation for a given space.

Conclusion

You don't have to use any of the above tips in creating your gadgets, but I hope that you at least were inspired to think about the designs and challenges that were discussed. I find that these practices improve both the application's usability and my own coding efficiency. I hope they can do the same for you. And, if you have tips of your own, I do hope you'll share them with the rest of the Google Desktop Gadget developers.

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.


Creative Commons License
This work is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License.