Creating and using custom controls

formsPlayer provides a powerful programming environment in which it is easy to keep functionality distinct from mark-up. In this section we explain why you might want to create custom controls, explain how custom controls interact with the XForms backplane, and provide a guide to creating controls of your own.

Introduction to custom controls

Encapsulation

There are a number of reasons why you might want to create a custom control, but perhaps the most common is in order to encapsulate some functionality, so that the main document remains clean and free of distracting code. A typical example of this would be where the control contains processing that we want to hide because it is irrelevant to the 'logic' of our main form. For example, we might want to show a nice looking clock on a page, but we really don't want to see a lot of SVG or XAML mark-up, and we certainly don't care about the calculations that are involved in working out the angle of rotation of the clock hands, or the amount of skew needed to get LED-style numbers. To use a clock in a form, all we really want to see is simple mark-up, like this:

<xf:output ref="clock" appearance="fp:LED" />

or this:

<xf:output ref="clock" appearance="fp:analogue-clock" />

In each of these examples, the value of the control is coming from the instance data, but the rendering is different for each control, based on the setting of @appearance. Our LED clock widget might look like this:

red-led-clock

whilst our analogue clock widget might look like this:

analogue-clock

Inheritance

The custom control support in formsPlayer also allows us to create a hierarchy of objects, with one widget being defined as an extension of another. To illustrate how this also helps encapsulation, imagine that we have created a custom control that binds to an XForms output and shows an image; a typical use of this control might be to show the BBC's JamCam for the Elephant and Castle, in London:

<xf:output
  value="'http://www.bbc.co.uk/london/travel/jamcams/cctv/543603.jpg'"
  mediatype="image/jpeg"
/>

Now, imagine that we also want to show a map of some geographical location; there are a number of servers available that will give you an image of a map that is centred at the location you specify, so it would seem pretty simple to use the image custom control that we just saw, as follows:

<xf:output
  value="'http://ssems1.esrin.esa.int/mapServer/mapServer?...'"
  mediatype="image/png"
/>

This retrieves the correct image, but the URL is extremely long, and very difficult to manage. More importantly, as far as our form is concerned the only thing we're really interested in is the location--the longitude and latitude values--rather than which particular server is used.

So instead of using the image custom control directly, we build another custom control that inherits from or extends this image custom control. This new control works out what the image URL should be, based on the longitude and latitude fed in, but hides that URL from our main form:

<xf:output value="'52;4'" appearance="geoposition" />

This means that were we ever to need to change to a new mapping server at some point, this would be easily done in the custom control's definition.

Our control might look like this:

Screenshot of the map custom control

Accessibility

One final advantage of using the custom control approach is that, since the 'value' of this new control is 52;4 rather than some long, meaningless URL, it makes the control much more accessible; a voice system that does not support visual maps would still be able to read aloud the values 52 and 4 to a user.

In the next sections we'll look at the custom control architecture in more detail, and walk through how to create some controls.

Using custom controls in a form

In this section we'll look at all the ways that a custom control can be used in a form. A custom control can be used anywhere that the base control can be used which is particularly useful when using custom controls based on xf:output, since they can be used in labels, such as for controls or in selection lists, messages, including hints and help, and so on. Custom controls can also make use of instance data in the same way that any other control can.

String literals

The simplest use of a custom control is when the 'value' associated with the control is set using an XPath string literal. Using the example of a custom control that shows a map, we might set the longitude and latitude as follows:

<xf:output value="'51.523004;-0.106859'" appearance="geolocation" style="width: 200px; height: 100px;" />

Using dynamic instance data

Once again using the example of a map control, if the location for the map could change, we can place the data in an instance and refer to it using a binding expression on the control:

<xf:model>
  <xf:instance>
    <instanceData xmlns="">
      <location>51.523004;-0.106859</location>
    </instanceData>
  </xf:instance>
</xf:model>
.
.
.
<xf:output ref="location" appearance="geolocation" />

The data could now be changed either by loading some new instance data--from a server or local file--or by the user interacting with another control on the form. For example, an xf:input control would allow the user to edit the location directly:

<xf:input ref="location">
  <xf:label>Change location:</xf:label>
</xf:input>

and any time this value was changed, the location of the map in the xf:output control would also be updated.

Another approach would be to use two xf:range controls to control the longitude and latitude independently:

<xf:model>
  <xf:instance>
    <instanceData xmlns="">
      <location long="51.523004" lat="-0.106859" />
    </instanceData>
  </xf:instance>
  <xf:bind nodeset="@long | @lat" type="xs:integer" />
</xf:model>
.
.
.
<xf:group ref="location">
  <xf:range ref="@long" incremental="true">
    <xf:label>Longitude:</xf:label>
  </xf:range>
  <xf:range ref="@lat" incremental="true">
    <xf:label>Latitude:</xf:label>
  </xf:range>
  <xf:output value="concat(@long, ';', @lat)" appearance="geolocation" />
</xf:group>

Note that since our custom control requires only one value as input, we have to concatenate the longitude and latitude values. Also note that by using @incremental on our xf:range controls we can update the image as the user moves the slider.

Calculated values

Of course, we could use both techniques; we could have a map that is centred on the value from the instance data, and then another map alongside it that shows the location that is on the other side of the world, calculated using the instance data for the first map:

<xf:model>
  <xf:instance>
    <instanceData xmlns="">
      <location long="51.523004" lat="-0.106859" />
    </instanceData>
  </xf:instance>
  <xf:bind nodeset="@long | @lat" type="xs:integer" />
</xf:model>
.
.
.
<xf:group ref="location">
  <xf:range ref="@long" incremental="true">
    <xf:label>Longitude:</xf:label>
  </xf:range>
  <xf:range ref="@lat" incremental="true">
    <xf:label>Latitude:</xf:label>
  </xf:range>
  <xf:output value="concat(@long, ';', @lat)" appearance="geolocation">
    <xf:label>Your location:</xf:label>
  </xf:output>
  <xf:output value="concat(@long - 180, ';', @lat)" appearance="geolocation">
    <xf:label>Other side of the world:</xf:label>
  </xf:output>
</xf:group>

Using xf:repeat to show a list of controls

Since our map is a control like any other, we can also use it as part of a xf:repeat. In this example we have some instance data which is a list of locations, and then we use a xf:repeat to render a map for each one:

<xf:model>
  <xf:instance>
    <instanceData xmlns="">
      <locations>
        <location long="51.501383" lat="-0.139239">Home</location>
        <location long="51.523004" lat="-0.106859">Office</location>
        <location long="48.851049" lat="2.282238">XTech 2007</location>
        <location long="51.163898" lat="-115.564413">WWW 2007</location>
      </locations>
    </instanceData>
  </xf:instance>
</xf:model>
.
.
.
<xf:repeat nodeset="location">
  <xf:output value="concat(@long, ';', @lat)" appearance="geolocation">
    <xf:label ref="." />
  </xf:output>
</xf:repeat>

Selection labels

It's also possible to use custom controls based on xf:output in selection lists. In this example we use the same list as before, but instead of a simple repeat, we use the locations to create a list of maps that the user can choose from:

<xf:model>
  <xf:instance>
    <instanceData xmlns="">
      <locations current-location="">
        <location long="51.501383" lat="-0.139239">Home</location>
        <location long="51.523004" lat="-0.106859">Office</location>
        <location long="48.851049" lat="2.282238">XTech 2007</location>
        <location long="51.163898" lat="-115.564413">WWW 2007</location>
      </locations>
    </instanceData>
  </xf:instance>
</xf:model>
.
.
.
<xf:output ref="@current-location">
  <xf:label>Current location:</xf:label>
</xf:output>
<xf:select1 ref="@current-location">
  <xf:label>Where are you now?:</xf:label>
  <xf:itemset nodeset="../location">
    <xf:label>
      <xf:output ref="." />:
      <xf:output value="concat(@long, ';', @lat)" appearance="geolocation" />
    <xf:label>
    <xf:value value="concat(@long, ';', @lat)" />
  </xf:itemset>
</xf:select1>

Messages, hints and help

One last use of custom controls that it is worth drawing attention to is their use in messages, help text and hints. For example, if we wanted a tooltip on an xf:input control, that helped the user by showing a map that reflected the current location, we could mark it up like this:

<xf:input ref="@current-location">
  <xf:label>Change location:</xf:label>
  <xf:hint>
    You may want to change your location. Your current location is here:
    <br />
    <xf:output ref="." appearance="geolocation" style="width: 150px; height: 150px;" />
  </xf:hint>
</xf:input>

Similarly, we might want to provide some detailed help to our users (when they press [F1] in a control), and part of that help text might require a map:

<xf:input ref="@current-location">
  <xf:label>Change location:</xf:label>
  <xf:help>
    This control allows you to choose a location. You are currently here:
    <br />
    <xf:output ref="." appearance="geolocation" style="width: 100px; height: 100px;" />
    <br />
    but you could go here:
    <br />
    <xf:output value="'51.523004;-0.106859'" appearance="geolocation" style="width: 100px; height: 100px;" />
  </xf:help>
</xf:input>

Both xf:hint and xf:help are specific forms of xf:message and as you would expect, custom controls could also be used inside messages. For example, the following is a simple message that is activated by a xf:trigger:

<xf:trigger>
  <xf:label>Get a message</xf:label>
  <xf:message level="modal" ev:event="DOMActivate">
    This is where you could be:
    <br />
    <xf:output value="'51.523004;-0.106859'" appearance="geolocation" style="width: 100px; height: 100px;" />
  </xf:message>
</xf:trigger>

In the next section we'll look at how we can build some controls.

The custom control architecture

As we saw in the previous section, custom controls extend the functionality of a normal XForms control, and can therefore make use of the same features. For example, if a ref or bind attribute is used on the control, it will automatically be bound to the specified data in the XForms model, and so receive notifications when either the data or the model item properties (MIPs) change.

Similarly, as long as the custom control dispatches the correct event to the model when data is changed by the user, the author does not need to worry about keeping the model up-to-date.

The events that can pass between the custom control and the XForms model can be represented as follows:

Informing the backplane that the control is ready

Once a custom control has completed any necessary initialisation, the fp-uiready event must be dispatched. Although the event is used to let the formsPlayer framework know that the control is ready, the target for the event is the custom control itself, and the event will bubble to the formsPlayer framework.

Dispatching the event is typically achieved like this:

var evt = this.ownerDocument.createEvent("Event");

evt.initEvent("fp-uiready", false, false);
this.dispatchEvent(evt);

Notification from the backplane that the data has changed

The XForms model provides notifications to forms controls that the value of the bound node has changed. The XForms framework in turn passes on this notification to the custom control, via the fp-putvalue event.

The custom control will typically register for this event in the constructor:

var theListener = this.document.getFeature("Events.listener", "2.0");

theListener.handler = this.handlerPutvalue;
this.parentElement.addEventListener("fp-putvalue", theListener, false);

A handler for the fp-putvalue event can use the Event object (as defined in DOM 2 Events) to obtain the new value, and will generally check that it is different to the previous value before doing something with it:

function handlerPutvalue(event)
{
  switch (event.type)
  {
    case "fp-putvalue":
      var newVal = event.newValue;

      if (this.currentVal != newVal)
      {
        //do something here
        this.currentVal = newVal;
      }
      break;

    default:
      break;
  }
  return;
}

Building a custom control

Defining a custom control requires us first to decide how it will be used by authors. This is important because we want to ensure that the expertise required to use our custom control is much less than the expertise required to build it. We can achieve this if we try as much as possible to leverage skills that authors already have. For example, if we were designing an LED display and we wanted to give authors the ability to set the colour of the digits, we should use the CSS color property, rather than defining some function. Similarly, if we were creating an analogue clock, we'd almost certainly want to allows authors to decide how large or small each use of the clock should be, and that could easily be done with the CSS width and height properties.

In this section we show which factors to consider when working out how a control will be used, how authors can link to custom controls and make them available to a form, and finally how to actually create custom control definitions--which we'll illustrate with a simple map widget, and some clocks.

Deciding on a custom control 'interface'

The first thing that we need to do when creating a custom control is to decide how we want it to be used, i.e., what is the mark-up that authors should use to make use of our new features?

One custom control that we're going to create will have an image that is a map, centred on a specified longitude and latitude. We'll therefore use the 'value' of the control to provide that location. However, we'd like to also control the size of the map, since sometimes we'd like to have thumbnails, and sometimes much larger images. The obvious way to do this would be to make use of the CSS properties width and height, and allow them to govern the size of the map. Finally, since we don't want the user to be able to update the value of the location we'll use xf:output as the control that we'll be modifying.

A typical usage of this 'interface' might be:

<xf:output value="'51.523004;-0.106859'" appearance="geolocation" style="width: 200px; height: 100px;" />

which gives us a map centred on the formsPlayer office in London.

We'll also create a number of clock custom controls, to illustrate the many different ways we could approach this. For our LED clock, we might want the background colour of our control to be used to determine the background of the clock, and we might want the text colour to set the colour of the digits. As before, since we don't want the user to be able to update the time we would use an xf:output, so our 'interface' might look like this:

<xf:output value="'14:52:00'" appearance="fp:LED" style="color: red; background-color: black;" />

red-led-clock

Choosing a base control

Custom controls are extensions to more basic controls so we'll always need some control to enhance. But it's a good idea to choose a base control that reflects in some way the functionality of the control you are trying to build. For example, if you are creating a video player widget, base it on xf:output rather than xf:range or xf:upload. Similarly, even if you are creating a complex control that is more like a dialog-box which is made up of lots of other controls--if ultimately all this control does is to allow a user to choose a colour from a palette then it can be based on xf:select1.

By building on a control that already has some of the behaviour you require, you automatically get a good fall-back mechanism if your forms are used on processors that don't support custom controls, and perhaps more interestingly, you also create the possibility of users replacing your custom control with one of their own choosing, from a personalised stylesheet.

Indicating which controls are custom controls

Since we are going to want some of our controls to be maps and others to be clocks, we need to provide a 'hook' that identifies which controls need to be extended.

Extensions are added based on rules defined using XPath, so we can add pretty much any hook we want. A common convention is to use the class attribute from XHTML or the appearance attribute from XForms. However, it is also possible to use a more dynamic approach, and add extensions based on the underlying data type in the XForms model. In both of our examples above we used the XForms appearance attribute:

<xf:output value="'52;4'" appearance="geolocation" />
<xf:output value="'14:52:00'" appearance="fp:LED" />

But we could also have used the HTML class attribute. In the following example, if a user browsed to our form using an XForms processor that did not have a clock custom control, they would still see the time in green:

<style type="text/css">
  .clock { color: green; }
</style>

<xf:output value="'14:52:00'" class="clock" />

In the next section we'll look at how the custom controls we create can be made available to authors.

Importing custom controls into a form

Linking to a set of bindings rules

We saw earlier how we generally provide an extension 'hook' on a base control that allows us to extend it with our custom functionality. The binding is expressed in a set of binding rules, so the first step is to provide a link to some rules. We do this by using the HTML link element, as follows:

<?xml version="1.0" encoding="utf-8" ?>
<html
 xmlns="http://www.w3.org/1999/xhtml"
 xmlns:xf="http://www.w3.org/2002/xforms"
>
  <head>
    <title>Map widget</title>
    <link rel="bindings" href="my-bindings.xml" />
  </head>
  <body>
    <xf:output value="'52;4'" appearance="geolocation" />
  </body>
</html>

Defining binding rules

The binding rules simply map some expression to a file that defines the behaviour we want. For example, to attach the functionality from the file geo.xbl to all xf:output controls that have appearance="geoposition" we would use the following rule (in my-bindings.xml):

<?xml version="1.0" encoding="UTF-16"?>
<bindings xmlns="http://www.x-port.net/bindingresolver/" xmlns:xf="http://www.w3.org/2002/xforms">
  <binding match="xf:output[@appearance='geoposition']/xf:pe--value" binding="geo.xbl" />
</bindings>

Note that we don't actually bind to the xf:output control as a whole, but the ::value part. This is because a control is actually made up of not just the interactive part (such as an input box), but also includes a label, as well as help, hint and alert messages. Since we may want to bind to each of these items with different custom controls, we always bind to the smallest--or most specific--part of a control that we can. (For more on this, see Anatomy of a control.)

Now that we've made the custom control available to a form, it can be used in any of the ways that the equivalent base control can be used, as we saw earlier in Using custom controls in a form. Our next step is to define the control.

HOWTO: A simple map control

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://libxh-apps.googlecode.com/svn/trunk/_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://libxh-apps.googlecode.com/svn/trunk/_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&amp;REQUEST=GetMap&amp;SRS=EPSG:4326"
              + "&amp;BBOX="
              + (Number(nLat) - 30) + ","
              + (Number(nLong) - 15) + ","
              + (Number(nLat) + 30) + ","
              + (Number(nLong) + 15)
              + "&amp;WIDTH=" + nWidth
              + "&amp;HEIGHT=" + nHeight
              + "&amp;LAYERS=DEM_30sec&amp;STYLES=&amp;FORMAT=image%2Fpng&amp;BGCOLOR=0xFFFFFF&amp;TRANSPARENT=FALSE&amp;EXCEPTIONS=application%2Fvnd.ogc.se_inimage&amp;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://libxh-apps.googlecode.com/svn/trunk/tutorials/custom-controls/map/.

Understanding the MVC separation

One of the key ideas at the heart of the custom control architecture in formsPlayer is the separation of the data from how it is rendered. XForms lends itself very well to this, since it was defined from the ground up with a Model-View-Controller (MVC) 'approach'. This means that data such as the locations we were dealing with in the simple map control tutorial, can be manipulated in other controls, or even used as the basis for other calculations, without affecting the rendering of the data.

This approach also means that we have a standard way that changes to the data are communicated to and from the controls, saving us the need to keep inventing new event names each time we create a new widget.

Although these points may seem obvious, we often see custom controls that have all of the functionality in the user interface. When this happens we obviously don't have an MVC architecture, since without some data there is nothing to put in a 'view'. That might sound like pedantry but by packing the control full of functionality and dropping the MVC approach, we dramatically reduce the usefulness of our control.

A good example is a clock control. Whether they use SVG, XAML, Java or C#, writers of clock widgets always put the timing element in the control itself. Take the Silverlight clock example; the code involves creating a nice looking analogue clock using XAML, but if we look at the code that actually keeps track of the time, we see this:

<Canvas Opacity="0" x:Name="parentCanvas" Loaded="setClockTime">
  <Canvas.Triggers>
    <EventTrigger RoutedEvent="Canvas.Loaded"
      <EventTrigger.Actions>
        <BeginStoryboard>
          <Storyboard>
            <DoubleAnimation x:Name="hourAnimation" Storyboard.TargetName="hourHandTransform" Storyboard.TargetProperty="Angle" From="180" To="540" Duration="12:0:0" RepeatBehavior="Forever"/>
            <DoubleAnimation x:Name="minuteAnimation" Storyboard.TargetName="minuteHandTransform" Storyboard.TargetProperty="Angle" From="180" To="540" Duration="1:0:0" RepeatBehavior="Forever"/>
            <DoubleAnimation x:Name="secondAnimation" Storyboard.TargetName="secondHandTransform" Storyboard.TargetProperty="Angle" From="180" To="540" Duration="0:1:0" RepeatBehavior="Forever"/>
            <DoubleAnimation Storyboard.TargetName="parentCanvas" Storyboard.TargetProperty="Opacity" From="0" To="0.7" Duration="0:0:4"/>
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger.Actions>
    </EventTrigger>
  </Canvas.Triggers>
  ...
</Canvas>

This mark-up creates a clock-face and hands for the hours, minutes and seconds (removed from this snippet to make it easier to read), and then animations are created which rotate the hands from 180 degrees to 540 degrees (i.e., a full 360 degree rotation), taking the correct amount of time for each hand--12 hours for the hour hand, 60 minutes for the minute hand, and one minute for the second hand. Although this looks roughly OK as a clock--in a separate document the control is at least initialised with the current time--we have no way of ensuring its accuracy, since once it has been initialised we are dealing only with animations and not the current time. But more important even than the accuracy, since the clock will only ever show the current time, it means that we can't use the same widget in other situations, such as:

  • to show multiple clocks, each in a different time-zone;
  • to show the time that a flight or train will depart and arrive;
  • to show the amount of time remaining in a presentation;
  • to show the time that a photograph was taken;
  • and so on.

However, if we can create a version of this Silverlight clock that does not move the hands itself but instead does nothing more than place the hands at the correct position based on whatever data it is given, then we would be able to re-use the widget in all of the places just mentioned. And in addition, we'd also be able to swap the widget out for a completely different widget in the future, if the need ever arose.

In the next section we'll illustrate these points by showing how we can create a custom clock control, using XAML and Microsoft's Silverlight.

HOWTO: A clock control using XAML and Silverlight

As with our simple map control, our first step is to decide how authors can make use of our control. A typical usage might be:

<xf:output value="'10:10:10'" appearance="xaml:analogue-clock" style="width:150px;height:150px;" />

This will create a static clock, 150px by 150px, showing a time of 12:10:10. We'll also ensure that the control can cope with full time and date formats, such as:

<xf:output value="'20010604T112000Z'" appearance="xaml:analogue-clock" style="width:150px;height:150px;" />

Now we know how authors will use the control, we can go ahead and build it. Once we're finished, it will look something like this:

Silverlight clock as a custom control in XForms/formsPlayer

The XAML definition of the clock

The definition we'll use for the visual side of the clock is pretty much the same as in the example we saw in the last section. The main change is that we don't need the animations that move the clock hands, since we'll be setting the hands based on the bound data. There is a fourth animation though, which fades in the clock, and we'll keep that.

We won't go into XAML in detail, but one point that needs drawing out is that for each attribute we want to modify, we need to provide a unique name for the element that contains that attribute. In the case of the clock, this has already been done, but were we creating the XAML from scratch we would need to follow this rule. To understand why we need to do this let's look at how the minute hand is defined:

  <Path Data="M -4, 16 l 3 70 3 0 2 -70 z" Fill="white">
    <Path.RenderTransform>
      <TransformGroup>
        <RotateTransform x:Name="minuteHandTransform" Angle="180"/>
        <TranslateTransform X="150.5" Y="145"/>
      </TransformGroup>
    </Path.RenderTransform>
  </Path>

The actual shape of the hand is defined by the Path element, but its orientation is defined by the Angle attribute on the RotateTransform element. So that we can write code to gain access to the attribute, we need to be able to locate the element, and to do this we use the XAML x:Name attribute, which uniquely names the element. Once we've located the attribute, setting it to some value--based on the time value fed into the control--will automatically update the display of the minute hand.

Managing the interaction with the XAML object

Our custom control will play the role of interacting with a XAML object on behalf of the backplane. (We'll show how to indicate which XAML file to use, below.) Since much of what is involved in doing this will be common to any control we develop that uses XAML, we have taken the functionality out into a common base class:

<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl">
  <binding extends="http://svn.x-port.net/svn/public/samples/chrome/com.microsoft.xaml/xf-pe-value.xbl">
    ...
  </binding>
</bindings>

Defining properties to hide interfaces

We saw earlier that positioning the hands of the clock requires setting the Angle attribute on each hand. Whilst we could use code to find this attribute each time we need it, this would make our code very specific, since the method for finding attributes in XAML is very different say, to SVG. Our custom control architecture provides a means to map properties in the XAML object to properties on the custom control. The mapping is handled in the XAML-specific code that we are extending, and all we need to do is specify the relationships:

  <binding extends="http://svn.x-port.net/svn/public/samples/chrome/com.microsoft.xaml/xf-pe-value.xbl">
    <implementation>
      <property name="hourOrientation" element="hourHandTransform" attribute="angle" />
      <property name="minuteOrientation" element="minuteHandTransform" attribute="angle" />
      <property name="secondOrientation" element="secondHandTransform" attribute="angle" />

With these mappings in place, we can now write code like this:

this.hourOrientation = 30;

which will have the effect of setting hourHandTransform.angle.

Specifying the XAML object to load

The next part of our custom control indicates the XAML file to load:

      <property name="secondOrientation" element="secondHandTransform" attribute="angle" />
      <constructor>
        this.firstChild.source = "analogue-clock.xaml";
      </constructor>

Finally, as with our map control, we need to provide an implementation for the setValue method, which will need to crack open the data input, and then use the hours, minutes and seconds to set each of the hands on the clock. First we declare the method and its parameter:

      </constructor>
      <method name="setValue">
        <parameter name="newVal" />
        <body>
          ...
        </body>
      </method>
    </implementation>
  </binding>
</bindings>

Next we parse the input value in order to get the hours, minutes and seconds:

        <body>
          try
          {
            String(newVal).match( /((\d{4})(\d{2})(\d{2})T)?(\d{2})[\:]?(\d{2})[\:]?(\d{2})[Z]?/ );

            var hours = parseInt(RegExp.$5, 10);
            var minutes = parseInt(RegExp.$6, 10);
            var seconds = parseInt(RegExp.$7, 10);

Now we can work out the angle of each hand. Each hour is 30 degrees, although the display will look better if we also add a sixtieth of that for each minute. Once we have the angle we can use the this.hourOrientation to set the correct attribute in the XAML object:

            var seconds = parseInt(RegExp.$7, 10);

            var angle = (hours / 12) * 360 + minutes/2;

            angle += 180;
            this.hourOrientation = angle.toString();

Finally, we can do the same thing for the minutes and seconds:

            this.hourOrientation = angle.toString();

            angle = (minutes / 60) * 360;
            angle += 180;
            this.minuteOrientation = angle.toString();

            angle = (seconds / 60) * 360;
            angle += 180;
            this.secondOrientation = angle.toString();
          }
          catch(e)
          {
            // some error handling
          }
       </body>

Using our custom control

All we need to do now is to create a bindings file:

<?xml version="1.0" encoding="UTF-16"?>
<br:bindings
 xmlns:br="http://www.x-port.net/bindingresolver/"
 xmlns:xf="http://www.w3.org/2002/xforms"
 xmlns:xaml="http://schemas.microsoft.com/winfx/2006/xaml"
>
  <br:binding match="xf:output[@appearance='xaml:analogue-clock']/xf:pe--value" binding="analogue-clock.xbl" />
</br:bindings>

Assuming that we call this file my-bindings.xml then we can now use the binding rules in the normal way:

  <head>
    <title>Silverlight Clock</title>
    <link rel="bindings" href="my-bindings.xml" />
    .
    .
    .
  </head>
  <body>
    <xf:output ref="clock" appearance="xaml:analogue-clock" class="clock" />
  </body>

If you have a recent version of formsPlayer installed, as well as a version of Microsoft's Silverlight then you can run this demonstration directly. If you'd like to obtain the source files to edit locally, they are available at http://libxh-apps.googlecode.com/svn/trunk/tutorials/custom-controls/silverlight-clock/.

HOWTO: Display streaming video using xf:output

Currently, the XBL bindings that we have made available allow for streaming video to be implemented in output via two players: Real Player and Windows Media Player.

To use them, simply put the URL of the stream in to the value attribute (this can be the result of an XPath expression if you like) and add an appropriate mediatype attribute ('audio/x-pn-realaudio-plugin' for Real Player and 'application/x-mplayer2' for Windows Media Player).

For example:

  <xf:output value="'mystream.ram'" mediatype="audio/x-pn-realaudio-plugin" />