Are you still writing gadgets that look the same whether they're inside or outside the Sidebar?
A cool feature of Google Desktop is the ability to display gadgets outside of the Sidebar (undocked). Gadgets can also be collapsed (minimized) to conserve space. A good gadget adjusts its UI to its current display state.
This article will show you how to:
The Google Desktop Weather Gadget changes its UI depending whether it's undocked, docked or minimized.
Undocked | Docked | Minimized |
---|---|---|
![]() |
![]() |
![]() |
The undocked, expanded version is the largest and flashiest. The graphics are high-quality and distinctive, ensuring the gadget is recognizable amidst other objects on the desktop.
The docked gadget has less space to work with, so graphics are smaller and some information is sacrificed.
Gadgets can be collapsed, or minimized, to the size of a title bar. In this state, users will appreciate any useful information you can provide within the confines of the title bar.
To summarize, here are some basic guidelines to follow for the different display states:
Now let's see these topics in action. We'll be creating a gadget
that monitors the system memory. The API offers methods to retrieve
memory statistics under framework.system.memory
.
Here are the specifications for the gadget:
Let's set up handlers that will be notified of state change events:
// Called when gadget is first opened. function onOpen() { ... // Assume the gadget is docked at first. // The onDisplayTargetChange handler will be notified if it is in fact // undocked. isDocked = true; // Handle notifications sent when gadget is docked or undocked. pluginHelper.onDisplayTargetChange = onDisplayTargetChange; // Handle notifications sent when gadget is minimized. view.onminimize = onMinimize; // Handle notifications sent when gadget is restored (maximized) view.onrestore = onRestore; ... }
Let's look at the events used by the gadget:
pluginHelper.onDisplayStateChange
fires just before
the gadget's display location changes, such as from the Sidebar
(docked) to a floating desktop window (undocked). The handler has a
single argument which specifies the new location.view.onminimize
fires when the gadget is minimized.view.onrestore
fires when the gadget is restored from the minimized state.The gadget keeps track of the current gadget state in two
variables: isDocked
and isMinimized
.
The handlers simply update those two variables accordingly. We resist the urge to add UI-related code here, since that code should be contained in a separate drawing function.
NOTE: During gadget startup,
pluginHelper.onDisplayStateChange
does not fire if the
gadget is docked. However, it does fire if the gadget starts up
undocked. That is why isDocked
is initially set to
true
in onOpen
.
Here is the code for the handlers:
function onDisplayTargetChange(displayTarget) { // Find out the new display mode if (displayTarget == gddTargetSidebar) { debug.trace('Display target changed to docked'); isDocked = true; refresh(); } else if (displayTarget == gddTargetFloatingView) { debug.trace('Display target changed to undocked'); isDocked = false; refresh(); } } function onMinimize() { debug.trace('Minimized'); isMinimized = true; refresh(); } function onRestore() { debug.trace('Restored'); isMinimized = false; refresh(); }
Here is the view.xml
that defines the UI:
<view height="40" width="200" onopen="onOpen()"> <div name="backgroundPane" width="100%" height="100%" background="#EEEEEE"> <div name="meterPane" height="20" width="180" x="9" y="13"> <div height="20" name="meterOff" width="180" background="#000000" /> <div height="20" name="meterFull" width="180" background="#0000FF" /> <label name="percentLabel" height="20" x="50" width="80" align="center" vAlign="middle" color="#FFFFFF" bold="true" /> </div> <div name="statusArea" height="20" width="100%" x="10" y="40"> <label name="statusLabel" height="20" width="100%" /> </div> </div> <script src="main.js" /> </view>
The width of meterFull
reflects the percentage of
memory in use. The statusArea
displays the current
memory usage in MB and only appears when the gadget is undocked.
We now draw the gadget within a single function. In many cases, it is easier to centralize all drawing-related code as we've done here. That way the drawing logic is easier to understand.
// Draw the gadget. function draw(memoryData) { var formattedPercentage = (memoryData['usedPercentage'] * 100).toFixed(2); if (isMinimized) { // The gadget is minimized. // Write status to the caption. var caption = ''; caption += formattedPercentage + '% ' + strings.MEMORY_USE; // Set the caption (title bar). view.caption = caption; // Nothing else to do, return. return; } else { // The gadget is not minimized.. // Set to normal caption. view.caption = strings.GADGET_NAME; } // Expand the width to reflect percentage of memory used. meterFull.width = memoryData['usedPercentage'] * METER_WIDTH; // Write the percentage status. percentLabel.innerText = formattedPercentage + '%'; // Special handling for when the gadget is docked or undocked. if (isDocked) { // Remove background. backgroundPane.background = ''; // Set appropriate height. view.height = DOCKED_HEIGHT; // Hide the status area. statusArea.visible = false; } else { // Show the background image. backgroundPane.background = 'background.png'; // Set appropriate height. view.height = UNDOCKED_HEIGHT; // Show the status area. statusArea.visible = true; // Update the status area. var status = convertToMB(memoryData['used']) + '/' + convertToMB(memoryData['total']) + ' MB ' + strings.MEMORY_USE; statusLabel.innerText = status; } }
And now the finished product...
Undocked | Docked | Minimized |
---|---|---|
![]() |
![]() |
![]() |
I hope that you consider these things the next time you write a gadget. At the very least, remember to take full advantage of the title bar.
If you want to run the gadget or view the source, you can download it here.
Thanks for reading, and have fun!