Wednesday, June 17, 2009

What's a gadget?

A gadget is an application that is specified via an XML file, which can contain HTML and hence JavaScript and even Flash code. There are two types of gadgets: those whose content is stored in the specification (HTML gadgets) and those whose content is stored at some network-reachable location specified by a URL (URL gadgets). We confine our discussion here to HTML gadgets (the default type). A gadget can store data persistently, run JavaScript code, and download content from remote websites.

Gadgets differ in terms of their configurability (i.e. the extent to which a user can specify his preferences for gadget operation and/or look-and-feel) as well as in whether the user inputs information into the gadget or instead merely receives info from the gadget (that is, whether communication flow is bi-directional in an ongoing basis). For example, many gadgets for news sources (e.g., the Wall Street Journal, Scientific American) at present provide few options for user configurability; also, the flow of information is mostly uni-directional — from the content provider to the user. By contrast, some applications have initial configuration opportunities but thereafter the flow of information is uni-directional (from the gadget to the user); e.g., a Clock application is initially configured with time zone but thereafter merely informs the user of the current time. There's no notion of the user interacting with that time in some way. Finally, some gadgets may or may not have user-configured settings but do have ongoing user input. For example, the to-do list gadgets involve the user providing information (i.e., items to be done); hence, the flow of information is bi-directional: from the gadget to the user ("this is your current list") and from the user to the gadget ("add item X" or "delete item Y").

Is there such a thing as a gadget mashup? I.e., a gadget which gathers information from more than one webpage? E.g., could I write a gadget which grabbed data from boxofficemojo (box office receipts), rotten tomatoes (movie reviews), and fandango (or some other movie-time site)? YES. I.e., a gadget is not restricted to only interacting with a single target location, right? YES. And a gadget can read/write from the network, correct? YES.

One future thought: Presently, we think of each OpenSocial app being deployed to a single container (i.e., social network such as Orkut or Linked-In); the next logical step is for an app to work across containers, i.e., to be a mashup between my Orkut profile/network AND my Linked-In profile/network. Not sure what the OpenSocial API offers w.r.t. this inter-social-network interaction.

Making Gadgets Accessible and Installing Them
Examples of gadgets can be found here. Enabling the public to use a gadget requires making the gadget world-reachable. There are a couple options for achieving this; a person can host a gadget as part of a website, the same way that he would host an HTML page. Also, Google provides two free alternatives for gadget hosting: (1) the Google Gadget Editor (GGE) and (2) Google Code Project Hosting. Once you've built a world-reachable gadget, you can list it in a directory (which people peruse to find worthwhile gadgets). Then an individual can add your gadget to some webpage (i.e., install the gadget) by inserting a "script" tag on the given webpage. The script's "src" attribute will point to the given gadget's XML file (i.e., contain that XML file's URL). That URL may also encode display (or other) preferences for the gadget.

Note that with this approach, one doesn't need to worry that his local version of a gadget (XML file) is out-of-sync with the remote version of the gadget because the user doesn't store the XML file locally but rather points to the remote version. (The user's browser may actually cache a local copy of the gadget when gadget execution is first triggered. But presumably this local copy would be refreshed the next time that the gadget's execution was triggered — which could be mere seconds later.)

Gadget components
A gadget specification (or <Module> element in XML) — such as this one — contains a few high-level elements: <ModulePrefs> (encodes gadget properties and preferences, such as the gadget's title, author, desired size), <UserPref>'s (used for persistently storing data and configuring or personalizing a gadget) and <Content> (controls the gadget's operation). <Content> contains HTML which itself can contain JavaScript etc.

<UserPref> can be used to store application state persistently; e.g., a user's grocery list or preferred background color. The values for user prefs are updated automatically when the user types into the "user preferences edit box" as below with the "Preferences for Rowan" box. When the user interacts with the gadget (entering a grocery item or a URL of a photo), the <UserPref> portion of the gadget's XML file is automatically updated accordingly. That's why, in a gadget's JavaScript code, we won't necessarily see any calls to set(...) preference values.

The code in the gadget's <Content> section can obtain and use the user's (stored) preferences via the gadgets.prefs API. This API can also be used to programmatically set user preference values (so long as setprefs is included). E.g., in the below, we see that three user preference variables (myname, myphoto, mychoice) are defined, each as a <UserPref>. The values for those variables are set when the user types into the user preferences edit box ("Preferences for Rowan" in this case) as discussed above. In the <Content> section below, we see JavaScript code that obtains the values of those variables using prefs.getString() and prefs.getBool(); this retrieves the stored value for the named variable. Note that the Prefs API also supports prefs.getInt().
User Prefs section contains:
<UserPref name="myname"
display_name="Name" default_value="Rowan"/>
<UserPref name="myphoto"
display_name="Photo"
default_value="http://blah.com/rowan-headshot.jpg"/>
<UserPref name="mychoice"
display_name="Show Photo?"
datatype="bool" default_value="true"/>

Content section contains:
var prefs = new gadgets.Prefs();
var html = "hello " + prefs.getString("myname") + "!";
if (prefs.getBool("mychoice") == true) {
html += '<img src="' + prefs.getString("myphoto") + '">';
}

For purely programmatic manipulation of user preferences, a gadget might define a method that is invoked onload; this method would obtain the values of various of the user's preferences and modify the gadget's display accordingly (e.g., setting the background color according to the user's preference). The gadget might also contain a method which stores user preference values; the trigger for this might be onmouseout (when the mouse moves outside the boundaries of the object) or onblur (when the object loses focus).

For XML or HTML code (i.e., within the <ModulePref> or <UserPref> sections OR within the HTML part of the <Content> section), you can obtain a particular user preference value via: __UP_userpref__, where userpref is the name of the desired user preference (e.g. myname, myphoto).

If you do not want to let a user modify some value and you want to store that value persistently (e.g., the user's high score for a game), you create a UserPref and give it type "hidden." You can modify the value of that variable programmatically, but the user will not be able to modify that variable's value.

Sharing a Gadget and Its Data
Moreover, one can also have shareable user preferences; e.g., a single list variable that is shared by multiple users (of the same gadget). Then, instead of just the one person modifying the shopping list, multiple users are modifying the same list. It appears that every gadget can by default be shared. That is, every gadget (that I've seen!) has the menu option "Share this gadget." Configuring a gadget to support shareable prefs entails adding <optional feature="shareable-prefs"/>. Depending upon a gadget's configurability and whether the user provides input to the gadget in an ongoing basis, sharing takes on different meanings.
  • For a gadget which has minimal configurability and no ongoing user input, there is little real sharing going on since users don't modify or interact with the gadget in any interesting way (there isn't any user-supplied data and hence nothing to share). E.g. the Wall Street Journal gadget as of time of writing.
  • For a gadget which has some configurability but minimal ongoing user interaction, there might be the option for a user to "share his configuration settings" with sharees. E.g. the Simple Clock gadget as of time of writing.
  • For a gadget which has ongoing user interaction, we can not only allow a sharee to "View my content," but we can also allow sharees to "View and edit my content." An example of this are the to-do list and post-it note gadgets where each sharer can contribute to a single (shared) list.
Gadget layout on iGoogle
One can add a gadget to his iGoogle page or to some other gadget container. On the iGoogle page, by default, the JS for all gadgets appears to run in the same execution context. Each gadget is defined as part of some section via the HTML <div> tag as illustrated below. A division can define its own function to be invoked on certain input events, including: onclick, ondblclick, onmousedown, onkeypress, and so on. Hence, if one clicks his mouse while that mouse is within the division for gadget X then gadget X's function for onclick will be invoked (if it exists). Presumably divisions are disjoint portions of the screen real estate, so there is no ambiguity about which division should receive an event. I have 20+ gadgets installed on my iGoogle page and the following HTML code exists for each:

<div id="left_nav_m_110_title" class="gadget_title">
<a id="lnat_110"
href="#"
title="To-Do List"
onclick="_IG_PushHistory('max110', 'selectMod','');return false;"
>To-Do List
</a>
</div>

The To-Do list gadget to which I'm referring lives here. Like all gadgets, it is defined by an XML file. Once the gadget has been installed onto a container page (e.g., iGoogle), it generates this JavaScript. Everywhere we see __MODULE_ID__ in the XML file is replaced with a "110" in the installed gadget's JS. This number identifies this particular gadget on my iGoogle page; the naming convention is presumably to ensure that there is no naming conflict with some other gadget's JavaScript function. This is necessary because all of the JavaScript for all of the gadgets on a person's iGoogle page appears to run in the same execution context.

Gadget execution
All of the JavaScript for all of the gadgets on a person's iGoogle page appears to run in the same execution context. That is, a gadget is not contained within its own iframe (for which a separate execution context would be created) but rather all of the JS of all of the gadgets is in the same HTML page. I actually have the ToDo list gadget installed three different times on my iGoogle page; each version contains a different type of list, one is for personal To-Dos, another for research-related To-Dos, and so on. So that means that my iGoogle page contains three copies of the same functions, each having slightly different names; e.g., loadToDos110, loadToDos117, and loadToDos109, corresponding to gadgets 110, 117, and 109, respectively.

As noted above, part of what spurred this investigation into gadgets was me wondering where a social networking app executes. And we know that the OpenSocial API (which is implemented by individual social networking sites or containers, such as Orkut) contains the gadgets API. So the following is, at present, my best conjecture as to the answer to that question. Let's imagine that Suzy is an Orkut user and installs an app on the Orkut site and gets her five closest Orkut friends to do likewise. When Suzy's browser visits the Orkut site and she logs into her account, presuably the application's JavaScript code is delivered to and executes in Suzy's browser (rather than executing on the Orkut web servers, for example, and just sending Suzy's browser some display code). That application may invoke an OpenSocial API function which itself calls out to the Orkut servers to obtain Suzy's friend list or similar. Think of this as the API function containing an RPC client stub which calls the RPC server, which runs at Orkut. This answers the question about how the OpenSocial API function interacts with the data stored on the Orkut site (i.e., the data that does not live in Suzy's browser).

Gadget security
Evidently there is a library that you can include as a "Required feature" of your gadget which"isolates" your gadget from other gadgets running on the same page: the locked-domain feature. No details on how this is implemented.

Note that if your gadget requires cookies, you may run into some trouble with IE and Safari. The reason is that a gadget is typically considered a "third party site" because the party hosting the site (e.g., iGoogle) is not the same as the party hosting the gadget (e.g., gadgetsrus.com). So in this scenario, the gadget code is running in an iframe on the iGoogle page. Evidently cookies can only be set for the containing webpage (e.g., iGoogle.com) and it would probably be a security violation to allow gadgetsrus.com to save a cookie for iGoogle.com (could open the door for spoofing attacks etc.). The best solution here would be to enable a cookie to be set for an iframe.

Remote Content
Can only download from a URL; can't use general sockets API to connect to arbitrary host/port. And even more specifically, the documentation suggests that only website URLs are legitimate (vs. FTP URLs).

Gadget privileges
Some basic issues in creating a social networking application include identifying the privileges needed by the application in order to serve its purpose and creating an installation and then execution environment (for the app) that confines the app to only access the data it needs and is authorized to access.

The Legacy Gadgets API
When I look at the code for several gadgets (NYT, Wikipedia, ToDo list, WSJ, ...), I see several calls to the legacy gadgets API, as identified by prefix _IG_ and as listed here. For example, there are calls to:
  • _IG_FetchContent, _IG_FetchXmlContent, _IG_FetchFeedAsJSON : Fetches content at the specified URL then invokes the given callback function upon completion of download.
  • _IG_GetCachedUrl : The passed URL identifies a file that is currently being cached; this function returns the proxy URL for that cached version of the file.
  • _IG_Analytics : Record a page view to a Google Analytics account. There are two arguments: the ID for the Analytics account and the path for the virtual page (whose hit counter should be incremented).
  • _IG_Prefs : For getting/setting UserPref's.
  • _IG_Tabs : Creates new instance of a Tabs object, which itself represents a set of tabs.
  • _IG_AdjustIFrameHeight : Requires the dynamic-height feature.
  • _IG_RegisterOnloadHandler : Pass a callback function that should be invoked when this page has completed loading.
  • _IG_RegisterMaximizeHandler : Presumably this enables setting a callback function for if the given window is maximized.
  • _IG_DD_Create :
So the above were the calls within the XML files that correspond to the different gadgets. When I look at my iGoogle page (the one which references each of these gadgets), I see other invocations of gadgets API functions, including:
  • _IG_AddEventHandler, _IG_AddModuleEventHandler, _IG_AddCustomEventHandler, _IG_AddDOMEventHandler
  • _IG_RemoveDOMEventHandler, _IG_RemoveModuleEventHandler
  • _IG_TriggerCustomEvent
  • _IG_callPostLoad
  • _IG_NavigateToGadget
  • _IG_LN_init
  • _IG_FormatLeftNavTitles
  • _IG_Callback
  • _IG_PushHistory
  • _IG_SetTitle
  • _IG_DD_init, _IG_DD_open, _IG_DD_create
  • Object types: _IG_inlinedTransitionStrategy, _IG_MiniMessage, _IG_iFrameTransitionStrategy, _IG_noMaxSupportTransitionStrategy
  • ...


No comments:

Post a Comment