
This section assumes that you have already read and understood the Time and Simple Image examples.
The Geocode widget looks up addresses using the Google Maps Geocode API and adds this information to items on the page. Unlike Simple Image, Geocode uses the Widget API directly.
The Configuration File for Geocode is quite similar to that for Time.
<widget title="Geocode"
description="Convert an address into a latitude and longitude"
icon="http://mashmaker.intel.com/icons/world_link.png"
version="12">
<feature name="incremental"/>
<settings href="http://mashmaker.intel.com/newwidgets/geocode_settings.html"/>
<content hidden="true" href="http://mashmaker.intel.com/newwidgets/geocode.html"/>
</widget>
The new features here are:
- We use a settings tag to specify a URL for the settings panel
- We make the main widget frame hidden, since this widget has no top-level visualization
- We enable the incremental feature, to get notified when the underlying data changes
The settings panel is just a simple HTML document containing javascript that talks over the Mash Maker Widget API. As with a widget URL, you can inspect the HTML for a widget settings panel by simply loading its URL into your web browser.
You can find the settings page here:
http://mashmaker.intel.com/newwidgets/geocode_settings.html
Open it in your browser and select "view source".
We'll start by going over just the plain HTML, then we'll cover the javascript part:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="stylesheet" href="http://mashmaker.intel.com/style/settingstyle.css"/>
<!-- javascript goes here -->
<body>
<table class="mm-table">
<tr class="mm-oddrow">
<td class="mm-name">Target items:</td>
<td class="mm-val"><select class="mm-select" id="path" onchange="refreshSettings()"/></td>
</tr>
<tr class="mm-evenrow">
<td class="mm-name">Address property:</td>
<td class="mm-val"><select class="mm-select" id="prop" onchange="refreshSettings()"/></td>
</tr>
<tr class="mm-oddrow">
<td class="mm-name">Information to add:</td>
<td class="mm-val">
<div><input type="checkbox" id="showaddr"/>address</div>
<div><input type="checkbox" id="showzip"/>post code / zip code</div>
<div><input type="checkbox" id="showlnglat"/>longitude, latitude</div>
<div><input type="checkbox" id="showlng"/>longitude</div>
<div><input type="checkbox" id="showlat"/>latitude</div>
<div><input type="checkbox" id="showlocality"/>locality</div>
</td>
</tr>
<tr class="mm-evenrow">
<td class="mm-name">Load Behaviour:</td>
<td class="mm-val">
<div><input type="radio" id="request-true" name="request" value="true"/>request individually</div>
<div><input type="radio" id="request-false"name="request" value="false"/>all at once</div>
</td>
</tr>
<tr class="mm-oddrow">
<td />
<td class="mm-val">
<button onclick="refresh()">refresh</button>
</td>
</tr>
</table>
<br/>
</body>
</html>
Nothing particularly exciting is going on here. We use the http://mashmaker.intel.com/style/settingstyle.css style sheet, which helps one make one's settings panel look like those of the standard Mash Maker widgets.
The settings elements themselves are just standard HTML form elements.
The interesting work in the settings panel is done by the javascript:
<script type="application/x-javascript" src="http://mashmaker.intel.com/v12/mashmaker_api.js"></script>
<script type="text/javascript" src="http://mashmaker.intel.com/v12/mashmaker_settings.js"></script>
<script type="text/javascript">
//<![CDATA[
var global_settings;
function mashmaker_widget_settings(args){
global_settings = args.settings;
updateUi();
}
var mashmaker_widget_init = mashmaker_widget_settings;
function refreshSettings(){
updateData();
updateUi();
}
var global_checks = ["showaddr","showzip","showlnglat","showlng","showlat","showlocality"];
function updateUi(){
mashmaker_settings.setPathSelect("path","",global_settings.path,{subprops:true});
mashmaker_settings.setPathSelect("prop",global_settings.path,global_settings.prop,{text:true});
mashmaker_settings.setChecks(global_settings,global_checks);
mashmaker.setHeight(document.body.offsetHeight);
document.getElementById("request-"+global_settings.request).checked = true;
}
function updateData(){
global_settings.path = document.getElementById("path").value;
global_settings.prop = document.getElementById("prop").value;
mashmaker_settings.getChecks(global_settings,global_checks);
global_settings.request = document.getElementById("request-true").checked;
}
function refresh(){
updateData();
mashmaker.setSettings(global_settings);
}
// ]]>
</script>
In addition to including the mashmaker_api, we also the Settings Library, which contains a number of helper functions that simplify the process of creating and managing a settings panel.
Let's go through this code in more detail.
function mashmaker_widget_settings(args){
global_settings = args.settings;
updateUi();
}
var mashmaker_widget_init = mashmaker_widget_settings;
The settings panel has two Entry Points, mashmaker_widget_init and mashmaker_widget_settings. In this case they are defined to be the same function. mashmaker_widget_settings is called when the settings are changed from the main widget frame.
Both of these functions are passed a settings object as part of their argument package. The structure of this object is entirely up to the widget.
Managing the Settings Interface
var global_checks = ["showaddr","showzip","showlnglat","showlng","showlat","showlocality"];
function updateUi(){
mashmaker_settings.setPathSelect("path","",global_settings.path,{subprops:true});
mashmaker_settings.setPathSelect("prop",global_settings.path,global_settings.prop,{text:true});
mashmaker_settings.setChecks(global_settings,global_checks);
mashmaker.setHeight(document.body.offsetHeight);
document.getElementById("request-"+global_settings.request).checked = true;
}
UpdateUI populates the settings interface based on the current settings. It uses the setPathSelect function from the Settings Library to fill in the select nodes with options corresponding to paths that match the widget's requirements.
The call to setHeight sets the height of the settings iframe to match the size of the document.
When the user clicks the refresh button, the widget converts the contents of the form elements back into settings data, and then passes these settings to Mash Maker and the main widget frame using setSettings.
function updateData(){
global_settings.path = document.getElementById("path").value;
global_settings.prop = document.getElementById("prop").value;
mashmaker_settings.getChecks(global_settings,global_checks);
global_settings.request = document.getElementById("request-true").checked;
}
function refresh(){
updateData();
mashmaker.setSettings(global_settings);
}
You can find the complete web page for geocode here:
http://mashmaker.intel.com/newwidgets/geocode.html Open it in your browser and select "view source".
In the following sections we'll walk through the various parts of this code.
var global_settings = null;
var global_done = {}
function mashmaker_widget_init(args){
global_settings = args.settings;
if(!global_settings){
guess_settings();
mashmaker.done();
return;
}
mashmaker.getData(0,global_settings.path, [global_settings.prop],false,process_nodes);
}
function guess_settings(){
global_settings = {request:"true",showlatlng:true,showzip:true};
var props = ["address"];
mashmaker_settings.guessItemPath("",["address"],function(path){
global_settings.path = path;
mashmaker_settings.guessPropPath(path,["address"],function(prop){
global_settings.prop = prop;
mashmaker.setSettings(global_settings);
});
});
}
function mashmaker_widget_settings(args){
global_done = {};
mashmaker.removeAll();
mashmaker_widget_init(args);
}
If the widget is initialized without any settings then it will try to guess appropriate settings based on the paths available. It does this using guessItemPath and guessPropPath from the Settings Library.
On mashmaker_widget_settings Geocode calls removeAll to remove everything it had added to the page with previous settings, and then starts from scratch with the new settings by calling mashmaker_widget_init.
Geocode calls getData to get the data that needs to be geocoded, following the path in settings and requesting the property given in the settings.
function mashmaker_widget_addProp(args){
mashmaker.getData(0,global_settings.path, [global_settings.prop],false,process_nodes);
}
function mashmaker_widget_removeProp(args){}
Geocode enables the incremental feature. It will thus receive mashmaker_widget_addProp and mashmaker_widget_removeProp calls when the data on the page changes.
We just do a simple implementation of addProp and removeProp. When mashmaker_widget_addProp is called, we execute getData again on the entire document to see if the data has changed. The global_done array is used to track which nodes we have already geocoded, to prevent duplicated work. mashmaker_widget_removeProp is just stubbed out.
A more sophisticated implementation would only follow paths inside the modified node, and would remove geocode information from nodes that no longer had addresses. We plan to add library support to make this really easy in the near future.
function process_nodes(nodes){
var addresses = get_addresses(nodes);
loadCache(addresses,function(){
if(global_settings.request){
for(var i in nodes){
do_request(nodes[i]);
}
}else{
do_geocode(nodes,get_address,enhance_node,mashmaker.done);
}
});
}
function do_request(node){
if(global_done[node.id]) return;
if(get_address(node)){
mashmaker.addIcon(node.id,null,"geocode the address of this item",function(){
do_geocode([node],get_address,enhance_node,mashmaker.done);
})
}
}
function get_address(node){
var prop = node.props[global_settings.prop];
if(prop && prop.text){
return prop.text;
}else{
return null;
}
}
function get_addresses(nodes){
var addresses = [];
for(var i in nodes){
var addr = get_address(nodes[i]);
if(addr){
addresses.push(addr);
}
}
return addresses;
}
process_nodes asks our geocode library to load its cache (the details of this are not important) and then checks to see whether the settings say it should gecode all items now, or ask the user to request geocoding for individual items by clicking on an icon.
If the settings say the user must request geocoding, then it calss do_request, which checks that it has not already added an icon, and then adds a request icon. Otherwise it just calls the library do_geocode function to do the geocoding and pass the results to enhance_node.
function enhance_node(node,addrinfo){
if(global_done[node.id]) return;
global_done[node.id] = true;
if(!addrinfo){
mashmaker.addError(node.id,"geocode",null,"bad address","http://mashmaker.intel.com/docweb/Geocode_Widget.html",function(){});
mashmaker.itemDone(node.id);
return;
}
function regen(){
global_done[node.id] = false;
do_request(node);
};
if(global_settings.showaddr){
mashmaker.addTextProp(node.id,"good address",addrinfo.goodaddr,regen);
}
if(global_settings.showzip){
mashmaker.addTextProp(node.id,"zip code",addrinfo.postcode,regen);
}
if(global_settings.showlnglat){
mashmaker.addTextProp(node.id,"lng lat",addrinfo.lng+","+addrinfo.lat,regen);
}
if(global_settings.showlng){
mashmaker.addTextProp(node.id,"longitude",addrinfo.lng,regen);
}
if(global_settings.showlat){
mashmaker.addTextProp(node.id,"latitude",addrinfo.lat,regen);
}
if(global_settings.showlocality){
mashmaker.addTextProp(node.id,"locality",addrinfo.locality,regen);
}
mashmaker.itemDone(node.id);
}
The enhance_node function adds the geocode properties specified in the settings to a node. The calls to addTextProp specifies a function that should be called when the user clicks the close button on the text annotation. In this case, we respond by looking back to do_request, putting the icon back again.
Since enhance_node can be called as the callback from addIcon, it calls itemDone when it has finished processing the item. This is simply ignored if enhance_node had not been called from addIcon.
As you can see, the geocode widget is not particularly complex. Moreover, we expect the implementation of widgets like this to become even simpler in the future as we are writing a library that standardizes much of the behaviour common to widgets that compute properties of elements at a path.

