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


Intuitive User Interface - tips & tricks

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 border between users and developers is getting thinner, but you're a developer for sure if somewhere down the line you ask yourself: 'How can i do that?'. However, some simple things can be tricky, so for efficient problem-solving you'll need tricks ;).. and tips against common errors.

This article will show you how to:

Lots of elements in your gadget != a big main.xml

If you have to make a visual simulation of something in your gadget, you probably need a lot of elements. To make it realistic, they're also likely to disappear at some point (only to realize that sometimes new ones will appear). Now how should you cope with that? In a gadget i needed 30 small images to be able to generate a nice looking wave signal (like in the image).

The final effect would have to be that of 30 lines of code, each of them creating an
<img> object, as shown in the first column.

XML Elements
Figure 1: Drawing a wave

Final effect, but not necessarily what you have to type:

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

An easier way to do it..

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

Other cool things the appendElement method opens up:

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.
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/write all the properties and call all the element's methods, like this: alert(points[3].y);

Related methods are insertElement (which enables you to choose the actual place in the XML you want new stuff to be added) and removeElement. Also, all the presented methods are supported by the <div> object as well, so the XML shouldn't keep any more secrets :)

User actions translated into accessible ojects

You have a lot of events at your disposal in the Google Desktop API. Among them : onclick, onmouseover, onmouseout etc., relative to the mouse, and onkeypress, onkeydown, onkeyup etc., relative to the keyboard. These are triggered when and where something happens, but sometimes we also need to know exactly what happened :)

The event object is there precisely to give us such information. It doesn't seem seem very important at a first glance, but it's a tool which enables a natural response from a gadget. Sometimes we don't realize how fond we are on pressing the Return key after we enter a query in a search box (because something sets the search button as default so we don't have to click it) or on dragging things on a virtual surface just as we would in reality.

Have a look at how i made the run box from the gadget in the image. It basically involves assigning a function to the onkeypress event of the <edit> object and then just using the data provided by event:

Search box example
Figure 2: Using edit controls
//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();"
/>

As i'm writing this, i'm also working on a gadget which lets you drag stuff 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;
	}
}

Also, event.button and event.srcElement can prove to be very useful. The first returns which mouse button has been (or is being) pressed, and the second returns the actual element on which the function was triggered (so you can do, for example, alert(event.srcElement.x+' '+event.srcElement.y) to find out the element's position). So 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're closely related to the gadget's main feature. For example, gadgets which have to do with time have some features that come hand in hand, like time and date.

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, like shown below in the Ticker for Trekkers gadget:

Here's a snippet of the code used to generate this menu (some of the objects and variables had been previously defined, but it's important to notice 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/');
      } );
}

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

Another way to keep the interface uncluttered is to consider 2 possible scenarios: the user is happy with the present settings, or he/she wants to change them. In order to do that the mouse has to be moved over the gadget but with the help of the onmouseover and onmouseout events we can make the gadget sensitive to that.

Sensitive inteface
Figure 3: Interface which is sensitive to user actions

You can see that my DigiWatch gadget shows the time and the date all the time. That's its main function (first image). But when the mouse hovers over it (second image), it chooses to make the date invisible since it's less likely to change when you're looking at it :) and shows several other controls to help you customize the gadget, like a 24h/AmPm button, Help, Alarm and others.. That's why it's also very important not to forget about tooltips. It's a great way to give information about an element in the gadget - it has the advantage of appearing only when the user expects it.


Conclusion

All these things aren't mandatory for a gadget, but they have the rare quality of improving the program behavior while making the developer's life easier :) So that's why, if you find yourself in one of these situations, i kindly recommend the presented solutions.
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 could find a lot of inspiration for intuitive interfaces (to keep the article in mind) in other things which have 'star' in their names too (like Star Wars, Stargate :-) . I like that border between interface and functionality - it's what makes most of a program, i think. Anyway, i'm a software engineering student now, and you can see more of what i'm up to, thoughts or new gadgets at my website.