We've established the connection between our form and a list of controls (using link), and we've indicated the rules under which our functionality should be bound to some control (in my-bindings.xml); now we need to define the custom control itself, which we'll place in the file referred to in the binding definitions--geo.xbl.
Our simple control will be based on the standard image control. Since the image control handles its own initialisation and event registration, and because we don't have any special initialisation to do in our map control, then we don't need to add any specific handlers. This makes our control very straightforward, since all it will need to do is support the setValue method which will be called by the backplane whenever the bound data changes.
Since we've decided that the input to our control will be a string that contains a longitude and latitude:
<xf:output value="'51.523004;-0.106859'" appearance="geolocation" style="width: 200px; height: 100px;" />
then the setValue method will need to split the input value apart to get the two coordinates, and then use those values to create a URL for the map image. This is then placed into the src attribute in the image control.
Creating a document to hold custom controls
The first thing we need to do is create a document that will hold our custom control functionality. A bindings document can hold one or more control definitions, so the general format is to have one or more binding elements contained within a bindings element:
<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl">
<binding>
...
</binding>
<binding>
...
</binding>
</bindings>
Building on the image control
The binding element defines our control, and since it extends the image control, we indicate this using the extends attribute:
<binding extends="http://svn.x-port.net/svn/public/samples/chrome/image.xbl">
...
</binding>
It's possible for controls that are extended to in turn be an extension of another control, to any depth.
Declaring the setValue method
To create a method we need an implementation element, which will in turn contain as many methods and properties as we care to define for a control. In the simple example we're creating here, we need only one method--setValue--and it will take a single parameter, of the value passed from the backplane:
<binding extends="http://svn.x-port.net/svn/public/samples/chrome/image.xbl">
<implementation>
<method name="setValue">
<parameter name="newVal" />
<body>
...
</body>
</method>
</implementation>
</binding>
Implementing the setValue method
The actual code for the function is written in JavaScript. The first thing we want to do is obtain the longitude and latitude values from our input string, which we've decided will look something like this:
51.523004;-0.106859
then we need to have a regular expression that looks for a signed floating point number, followed by a separator, followed by another signed floating point number. To give a little flexibility, we might as well allow alternate separators, like a space or comma, and any number of them. This will allow any of the following strings to be parsed:
51.523004 ; -0.106859 51.523004,-0.106859 51.523004 -0.106859 51.523004,; ,; -0.106859
The code to obtain the longitude and latitude is simply:
<method name="setValue">
<parameter name="newVal" />
<body>
try {
newVal.match( /([-+]?\d*\.?\d+)[\,\s\;]*([-+]?\d*\.?\d+)/ );
var nLong = RegExp.$1;
var nLat = RegExp.$2;
You'll recall that we also decided to set the width and height of the map based on values from the CSS:
<xf:output value="'51.523004;-0.106859'" appearance="geolocation" style="width: 200px; height: 100px;" />
To achieve this we need to retrieve the current width and height, and pass it on to the map server:
var nLong = RegExp.$1;
var nLat = RegExp.$2;
var nWidth = parseInt(this.parentElement.currentStyle.width, 10);
var nHeight = parseInt(this.parentElement.currentStyle.height, 10);
Now that we have the longitude and latitude on which to centre the map, and we know how big the map should be, we can work out the URL for the image. Although there are many ways we could do this, in this particular control we're going to use a server that conforms to the OpenGIS Web Map Server (WMS) specification. This provides a service where the URL used to request an image can contain all sorts of information to indicate what part of the world the map should show, what type of image should be returned (JPEG, PNG, etc.), what type of map should be shown (terrain, weather, urban centres, etc.), and much more. To illustrate how WMS should work, the following query should work on any WMS-compliant server:
http://[some server and path]?VERSION=1.1.1&REQUEST=GetMap&SRS=EPSG:4326&BBOX=-30.106859,36.523004,29.893141,66.523004&WIDTH=200&HEIGHT=100&LAYERS=DEM_30sec&STYLES=&FORMAT=image%2Fpng&BGCOLOR=0xFFFFFF&TRANSPARENT=FALSE&EXCEPTIONS=application%2Fvnd.ogc.se_inimage&QUALITY=QUICKEST
The result will be a map image that has a bounding box from -30.106859,36.523004 to 29.893141,66.523004, is 200px by 100px, is a PNG file, and so on. The following is the image obtained from European Space Agency servers, using these exact parameters:
Returning to our code, we'll create our image by concatenating the parameters we need to create the URL, and then storing this value into our control's src property. The image control we've inherited from has conveniently given us a property to write the URL into; we don't need to worry how that property maps to an attribute in mark-up, since all we need to do is set this.src to some value:
var nWidth = parseInt(this.parentElement.currentStyle.width, 10);
var nHeight = parseInt(this.parentElement.currentStyle.height, 10);
this.src = "http://ssems1.esrin.esa.int/mapServer/mapServer?VERSION=1.1.1&REQUEST=GetMap&SRS=EPSG:4326"
+ "&BBOX="
+ (Number(nLat) - 30) + ","
+ (Number(nLong) - 15) + ","
+ (Number(nLat) + 30) + ","
+ (Number(nLong) + 15)
+ "&WIDTH=" + nWidth
+ "&HEIGHT=" + nHeight
+ "&LAYERS=DEM_30sec&STYLES=&FORMAT=image%2Fpng&BGCOLOR=0xFFFFFF&TRANSPARENT=FALSE&EXCEPTIONS=application%2Fvnd.ogc.se_inimage&QUALITY=QUICKEST";
}
catch(e)
{
// some error handling
}
</body>
</method>
If you have a recent version of formsPlayer installed then you can run this demonstration directly. If you'd like to obtain the source files to edit locally, they are available at http://svn.x-port.net/svn/public/samples/custom-control-tutorial/.

