This article was written and submitted by an external developer. The Google Desktop Team thanks Yannick Stucki for his time and expertise.
Listboxes, listitems, scrollbars...how do I put these things together?
Lists are key objects of many gadgets. Whether your gadget displays the latest RSS news feeds, or results from your local computer, you'll need UI objects that will display contents of lists.
In earlier versions of Google Desktop, you could only use contentarea
, which did its job but wasn't very flexible. Since March 2007, however, you had the option of using the more powerful
listbox
, which can be filled with listitems
.
There are many ways to use the listbox
that go beyond the scope of this introduction; however, I want to show you at least one way to take advantage of this very useful object.
As I've mentioned, the listbox
offers a lot of functionality. I want to focus on
a simple listbox
(as used in the
Movies
Gadget) that can display information from either an RSS feed or a desktop
query. This example will not demonstrate item deletion, selection (items
will be immediately deselected), or multiple selection.
To get started, create a listbox
in main.xml. It's best to wrap it in a div
as we'll see later.
<div name="listDiv" height="165" width="132" x="6" y="21"> <listbox height="100%" name="list" width="100%" x="0" y="0" itemWidth="100%" itemHeight="33" itemSelectedColor="#404040" itemOverColor="#404040" onchange="listOnChange()"/> </div>
Note: To make your list look nice, make sure that it doesn't crop the bottommost item. Notice that the height of the listbox
is a multiple of itemHeight
.
In this case, the total height is 165, and each item has a height of 33
(165/33 = 5).
If you set listbox.itemSeperator
to true, don't forget to account for the space occupied by the seperator(s).
In most situations, you need
to be able to fill the list on the fly, since you don't know what your RSS
feed is going to contain. This is an example of how you can add items to your list. Basically you wrap an
XML definition in a string. The outer item is a listitem
, inside of which you can put whatever you want:
list.appendElement("<listitem name=\"item\" tooltip=\""+item.name+"\"><label color=\"#FFFFFF\" tooltip=\""+item.name+"\">"+item.name+"</label><label color=\"#999999\" y=\"14\" tooltip=\""+item.name+"\">"+item.desc +"</label></listitem>");For this line of code, I assumed I had a JavaScript object called
item
with name
and
desc
properties. You can create such an object like this:
var item=new Object(); item.name="my Item"; item.desc="I am an Item";
I've also set a color
and added a tooltip
(be
sure to add it on both the listitem
and the label
). You could also add more labels, pictures, and so on—just make sure you align those objects properly. If the listitem
is too small for the objects, you can change the itemHeight
of the listbox
(not the listitem
).
You might have noticed that I didn't give the listitem
a distinct name, they are all named item
. In fact, I
could have left the name property empty altogether if I had used another powerful method to access each element: a hash table.
Hash table? Sounds difficult? It isn't at all.
A hash table is similar to an array, but instead of indexing its members with numbers (0, 1, 2, ...), you'd use strings ("item1," "breaking news," ...). Luckily, a JavaScript object can natively act as a hash table.
To begin, let's create a global variable:
var table=new Object();Next, we have to add references to the
listitems
in the hash table as we create them:
function append(item){ table[item.name]=item; list.appendElement("<listitem name=\"item\" tooltip=\""+item.name+"\"><label color=\"#FFFFFF\" tooltip=\""+item.name+"\">"+item.name+"</label><labelcolor=\"#999999\" y=\"14\" tooltip=\""+item.name+"\">"+item.desc "</label></listitem>"); }
Now let's assume you've loaded an RSS feed with the content stored in "title,"
"description," and "link." All you have to do is create an item
and call
append
:
var item=new Object(); item.name=title; item.desc=description; item.link=link; append(item);Do this for every element, but start from the back to the beginning so appended items will be inserted at the top of the list. This ensures that you have the same sorting as in the feed.
Because we have stored the whole item in the hash table, we only need to know the name
property to access it again.
function listOnChange(){ var item= new Object(); item=table[list.selectedItem.children(0).innerText] list.selectedIndex=-1; //show a page in the browser via item.linkNotice that
//or open a details view which shows more of item.description e.g. //showDetailsByName(item.name);
//or item.whatever additional information you gave it before
}
list.selectedItem.children(0).innerText
is the same as item.name
because we
stored the name in the innerText
of the first label; the label with the name inside is the first child: children(0)
.
We set selectedIndex
to -1 which unselects the item we just clicked.
If you are using a details view and you click the same item twice in a row, it re-opens the details view instead of closing it. This isn't the default behaviour that a user might expect. Therefore, I used the following code in the Movies Gadget:
var lastDetails=""; function showDetailsByName(name){ if (name==lastDetails){//the user clicked the same thing twice, close it instead of re-opening plugin.CloseDetailsView(); lastDetails=""; return; } //the name was different than the last details: open that details view lastDetails=name; //open details view //etc etc }
The easiest way to deploy a
scrollbar
is to set the autoscroll
property of the listbox
to true. This has two
possible disadvantages:
It really isn't hard to implement an advanced scrollbar; just alter your main.xml code to this:
<div name="listDiv" height="165" width="132" x="6" y="21"> <listbox height="200" name="List" width="119" itemWidth="100%" itemHeight="33" itemSelectedColor="#404040" itemOverColor="#404040" onchange="listOnChange()"/> <scrollbar enabled="true" height="184" hitTest="htclient" name="sb" width="10" x="124" y="-10" lineStep="14" max="200" orientation="vertical" background="0000000" thumbDownImage="scroll.png" thumbImage="scroll.png" thumbOverImage="scroll.png" onchange="sbOnChange()"/> </div>
Make the height of the listbox
slightly bigger than in the div
. We also have to shorten the listbox'
s width a bit to make room for the scrollbar
.
In the case of the Movies Gadget, I just set a transparent background for the scrollbar since the underlying background is black. The only image I added was the scroll button itself. If you don't set any images and the background is not fully transparent, the default gray scrollbar will be used.
I also didn't want the up/down arrows to appear, so I set the y
value to
a negative value and the height
bigger than in the div
.
The scrollbar
is purposely too long, and since it's wrapped in a div
, it will automatically be cropped.
Now we need to "connect" the div
and the scrollbar
:
function sbOnChange(){ list.height=NUMBER_OF_ITEMS*list.itemHeight; sb.max=NUMBER_OF_ITEMS*list.itemHeight-listDiv.height; if (sb.max<0) sb.max=0; list.y=-sb.value; }
You will need to know the number of items that have been added to the list, so keep track of this in your application. The trick here is if the list grows too long, we negatively offset the list and only the parts of the list which are in the div
area will be
displayed.
So far you have:
update()
function that can be called when a user clicks on a query button, or periodically by a setInverval()
:
function update(){ list.removeAllElements(); //don't forget that table=null; //clear the table table=new Object(); //fetch your content somewhere, e.g. via xmlHttpRequest
for (/*whatever*/){ //go through the results backwards; write what you need into a var item= new Object() append(item); } }
listbox
, such as listitem
deletion and multi-selection, which we skipped. Nevertheless, for many gadgets, the methods shown in the above examples will be the most common uses of listbox
.
If you want to see such a listbox
in action, have a look at the
Movies
Gadget. The gadget is open sourced so feel free to browse the source code.
All the functions shown here are taken (nearly 1:1) from there. You'll find
mostly everything in the file called main.js, except for the update function which is in
parsing.js.
I am currently a student and started programming a few years ago. It's always tough to produce complete programs yourself (that are actually used by others) because as a hobbyist programmer, you just don't have the time and resources of a fully-staffed team. That's why I love gadgets programming: gadgets range from very simple to fairly complex, but most importantly, you can get them out there and have people actually using them.
Besides programming, I love playing Rugby. And in winter, I go snowboarding in the Swiss Alps.
You can read more about my gadgets on my homepage. There you'll also find contact information if you need more help or have suggestions.
This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.