This handbook contains everything you need to know to build rich internet applications using formsPlayer and XForms. Since XForms is a W3C standard then nearly all of the ideas and code samples in this handbook can be used with other XForms processors.
Building an XForms application might be as simple as editing a file with Notepad and pressing F5 in the browser, or as complex as configuring servers and pipelines, and building an XForm on the fly. Either way you will need some kind of editor and some kind of XForms processor.
Since XForms are XML documents then any text editor can be used to create and edit them. If you've done much HTML development you may already have a favourite editor, in which case you are already up and running.
If you decide to do much serious development with XForms then you will probably find it easier to make use of an XML editor. Such editors will help keep your document structured correctly, and some editors have features that are specific to XForms. For example, XFormation will help you with your XPath expressions, whilst both Eclipse and XFormation can save a lot of time by automatically generating forms from schemas, WSDL files and XML data documents.
We'll discuss some of these editors in a separate tutorial, since to get started with this tutorial, you need nothing more than your current HTML editor, or simply use Notepad or Wordpad.
There are a large number of XForms processors, and which one is most appropriate for you will depend on a number of factors. Issues such as which devices and platforms you are targetting, and whether the XForms should be translated into something else or delivered to the end-user intact will all need to be considered.
The easiest development configuration is to install a client-side processor onto your development machine; even if the forms are later deployed in some other fashion it makes for an extremely quick design and development cycle if you develop them using a client-side XForms processor, running inside a browser, without the need for a server.
For a quick way to get going with this tutorial use formsPlayer, a plug-in for Internet Explorer 6 on Windows. You can get an automatic install (using a signed CAB file) from the project page, which also contains links to the support forums, screenshots, samples, lists of bugs and feature requests, and so on.
You'll find it easier to create new forms if you have an empty template that you can start with. Many XML editors will allow you to place such a template in a special folder so that it is available as a choice whenever you create a new XML document.
The template that follows is an HTML document that contain XForms; even if you plan to use XForms in some other language--and XForms was designed to make that possible--we would still recommend beginners to get acquainted with XForms within HTML.
To create the template:
<?xml version="1.0" encoding="utf-8"?>
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
<head>
<script src="http://lib-xh.googlecode.com/svn/trunk/xH.js" type="text/javascript">/**/</script>
<title>Empty XForm</title>
</head>
<body>
<p>Empty XForm</p>
</body>
</html>xforms-template.html.As you can see the document is much the same as an ordinary HTML page, with the following differences:
<?xml version="1.0" encoding="utf-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xs="http://www.w3.org/2001/XMLSchema" > <head>
We don't always need XML Events and XML Schema, but since this is meant to be a template that you can just use without thinking about it, we might as well add them here.
<script src="http://lib-xh.googlecode.com/svn/trunk/xH.js" type="text/javascript">/**/</script>
This script checks for XForms support on the browser that your users have used to load your form. If formsPlayer is present, that is used, and similarly if the Firefox XForms extension is available, that will be used. The script will also automatically install formsPlayer if the user is running Internet Explorer, and no version of formsPlayer is present.
Now you are ready to start building web applications with XForms! Let's see if we can create a working form, before we move on to the tutorial.
body element, replace the text Empty form with:<xf:input ref="q" incremental="true"> <xf:label>Search:</xf:label> </xf:input> (<xf:output ref="q" />)
Save As....Search;Anything you now type into the input control should also be displayed between the brackets:
If this does not happen then there may be a problem with your installation or browser security settings. For information on troubleshooting see the installation forum.
A key idea of our approach to building internet applications is making good use of standards-based languages, in particular XForms.
XForms is an exciting new language from the W3C that can be used to create anything from simple forms to complex Web 2.0 applications. XForms are dynamic, cross-platform, accessible, script-free...and 100% standard.
This handbook covers everything you need to know to get started with XForms. You'll start with a simple walkthrough to get your development environment up and running, then create two fully functioning applications--one that saves links to del.icio.us, and another that searches Flickr. Both use CSS-driven Ajax animations.
Once you've seen what XForms can do, building web applications will never be the same!
Handbook reviewed 2006-12-06, by MB.
XForms is a relatively recent standard from the W3C, designed to allow us to create sophisticated user interfaces using mark-up. This means that defining a user interface is much the same as using HTML, except that XForms has been designed from the ground up to cope with many of the things that we usually have to dive into script to do.
And XForms doesn't just make it easy to replace script in our applications, it also provides us with the means to manipulate and validate XML; unlike most languages that you might have used, XForms brings XML right into the heart of the language.
In this section we'll look at some of the things you can do with XForms, but if you want to jump right in and look at some mark-up, feel free to skip to Feet First...Some Basics
XForms makes it very easy to create dependencies between different parts of a web application. Of major significance is that these dependencies are established at the level of the data, before being reflected in the user interface. For example, say a form contains a data value for 'number of years at the current address' and another for 'previous address'; a rule could be created that says the latter need not be completed if the form-filler has been at their current address for less than three years.
In XForms this rule is defined completely independently of the actual user interface itself, but the XForms processor will ensure that any form control that is representing the 'previous address' fields will only be made relevant (i.e., available for the user to fill in), once the correct conditions are met.
It's also possible to define a rule that says the 'previous address' field is required if the form-filler has only lived at their current address for a certain amount of time. An XForms processor can prevent the form from being submitted to the server, if this condition is not met.
Since these 'business rules' are defined using a standard mark-up language and XPath statements, they become very easy to develop, maintain, and test. This is because, unlike traditional e-forms systems where such rules are tightly coupled with the user interface, in XForms there is a separate data layer within which the logic sits. (XForms was designed to make use of the Model-View-Controller pattern.)
In the following screenshot from the mortgage application sample (see the showcase area) a user has indicated that the purpose of the mortgage they are applying for is to buy a bungalow:
However, when the selection is changed to indicate the purchase of a flat, three further fields automatically become available to the user, to allow the capture of more information:

Since data-entry fields can be shown and hidden as other data is entered, it is important to provide some sort of indication to the user that the form has changed--particularly on large forms. formsPlayer makes various highlighting features available to the form author in a simple and concise way.
In the following screenshot we can see two highlighting techniques being employed at the same time; one is a yellow highlight that lasts for 2 seconds, and indicates to the user that some controls that were not previously available are now available, whilst the second technique draws the user's attention to some status information that is shown in green, for about 8 seconds:

Once the highlights have timed out, the form looks like this:

XForms allows us to define both the conditions for the highlighting and the highlighting itself in simple mark-up, and it is easy to add other effects--for example, a red background when a control becomes invalid.
Routing is a similar concept to relevance. However, XForms allows quite complex routing between form controls. For example, on a large form with many pages, it is often the case that to fix an error in one page the user needs to navigate back to a previous page. The HTML-like mark-up employed by XForms allows these types of relationships to easily be established.
In the following screenshot two form controls are in effect linked together since for them both to be valid, the length of time remaining on a lease cannot be greater than the full term of the lease:

However, if there is an error the user may not choose to correct the nearest control since the fault may be with the other part of the 'pair'. For example, if the full term of the lease was set to 60, and the remaining term set to 65, it's difficult to which of the two values is in error. To make things easier for the user, the form includes 'shortcuts' to the 'other' control; in the error messages below (only shown when the data is invalid) the text with the light blue background refers to the 'matching' control, and clicking on it causes the focus to be set to that form control:

In this example the controls are close by, but in a very large form this technique can greatly assist the user.
XForms provides extremely powerful mechanisms for providing help to users, both in the form of tooltips as well as [F1]-style help. Importantly, XForms has evolved to correct many of the limitations of current forms technologies, and allows for fully internationalised and accessible hints. In the following screenshot an XForms hint is used to show an image as well as formatted text:

Hints can also be multimodal--formsPlayer supports delivering hints and other messages using voice technology:

One other point worth drawing attention to is that XForms provides a mechanism for items such as hints and help (as well as error messages and form control labels) to be provided by external XML files, making it extremely easy to first define the structure and logic of a form, and then to 'drop in' any number of alternative files. This allows the same form to be provided to users who speak different languages.
XForms allows for many different types of data submission, providing an enormous degree of flexibility when talking to back-end systems. Data can be submitted in formats ranging from pure XML, to XML with embedded data (such as images or Word documents), through to the simple serialisation of name/value pairs. (This latter feature allows XForms to communicate with server-side systems that were designed to be used with HTML forms.)
In addition to this wide range of possible data formats, XForms also supports a variety of protocols. This means that XForms not only allows for data to be transferred using HTTP, but can also cope with the saving and loading of local files, as well as transferring data via email and even saving to local ODBC databases.
XForms has been designed to be platform independent, and processors for different devices are emerging. For example, organisations such as IBM are working on the use of voice with XForms, allowing the same form to be used unchanged on both a desktop PC and a touch-tone phone system.
x-port have a number of initiatives underway in this area, ranging from the user of voice prompts in forms, through to the development of Pocket PC and Mac versions of formsPlayer.
As always when learning a new language it's best to jump straight in and see the language in action, and leave learning the detailed rules of grammar until a little later. So in the following sections we'll build up a working form using a variety of features from the XForms lanuage, and we'll leave it to a later tutorial to explain the language itself. Any code in the following sections assumes that you've started with the template discussed earlier.
The simplest way to get data from a user is much the same as in HTML forms--by using the input control. However, although the controls have the same name, they do have a slightly different structure. We'll see an example first, and then look at how the mark-up differs from what you might be used to in HTML. The control we'll examine asks a user for a URL:
<xf:input ref="url"> <xf:label>URL:</xf:label> </xf:input>
The first difference from HTML that we can see, is that we must provide a label for the control. This shows how seriously accessibility has been taken in the design of XForms, and means it is now possible for voice systems to really know which text on a page applies to which control, rather than having to guess, as screenreaders are forced to do with today's forms.
The second change is that the data to be entered by the user is referred to using the ref attribute, rather than the name attribute.
The final change is that the name of the control has to have a qualifier on it, to show that it comes from the XForms language, and not the HTML language. This is pretty common when using a combination of XML languages, and distinguishing between languages is easily achieved by using a namespace prefix.
So far, so good. Now lets create another control--this time one that asks for a description for this URL:
<xf:input ref="description"> <xf:label>Description:</xf:label> </xf:input>
Hopefully no surprises there, since the only differences between this and our URL control are the text of the label, and the value used in the ref attribute.
If you want to follow along, copy the mark-up for the two controls into the body element in a document based on your template, and then view the document in Internet Explorer. As you can probably imagine, a form with just these two controls doesn't do much more than allow you to type in a URL and a description of it:
Before we go too much further, it's probably quite important for the more artistic amongst you to learn how to style our controls. The good news is that since XForms uses XML mark-up, then you can use everything you have learned about styling HTML documents.
To show how CSS is used, we'll restyle our form so that each control is on its own line, with all the data entry areas lining up vertically. We'll also add a nice dotted line to separate each control. The result will be something like this:
The CSS rules for this are pretty simple, so I'll show them first and then explain what it all means. To update your form, place the following in the head element, after the title, and then refresh the browser:
<style type="text/css">
html
{
font-family : "Trebuchet MS", Verdana, Helvetica, Sans-Serif;
font-size : x-small;
}
xf\:input,
xf\:output
{
width : 100%;
padding-top : 0.4em;
padding-bottom : 0.1em;
border-bottom : #d0c49d 1px dashed;
}
xf\:label
{
width : 12em;
vertical-align : middle;
margin-right : 0.2em;
}
</style>
The first part--the rules for the html element--will no doubt be familiar to you, and simply set a default font for our document.
The next set of CSS rules concern all input and output controls; to indicate that we want these rules to apply to the XForms input and output elements rather than elements with the same name from a different language (you never know!), we include the colon in the CSS selector. Note that since CSS uses the colon to specify pseudo-classes, we must escape it by placing a '\' in front:
xf\:input,
xf\:output
{
.
.
.
The first rule for input and output controls sets the width of the control to 100%, ensuring that each control takes up a whole line. Similarly the first rule for labels sets their width to 12em, which has the effect of lining up all the data entry areas.
Your first thought when looking at the previous rules may have been that setting the width of an input to 100% ought to have made just the data entry area take up an entire line, on a separate line from the label. This is a common assumption for people used to using HTML, and the reason is that the HTML input control is only a data entry area, with any labels that might apply to it being separate and taking up their own space. This means that there is no way to style the label for a specific control.
In XForms things are different. The control encloses its own label and the data entry area, like this:
Each of these parts can be styled independently, and since they have a common parent they can also be styled in relation to each other. For example, if we want the data entry area of all input controls to have a light green background, we would use this rule:
.input-value,
{
background-color : lightgreen;
}The resulting form would look something like this:
Try altering the values of the three parts of a control--the container, the label and the value--and see what happens. For example, see if you can make all labels bold, and place the label and data entry parts of controls on separate lines.
A common requirement in modern web applications is to show tooltips or mouseover hints to users to help them fill in a form more accurately. Simple tooltips using plain text can be created in HTML using the title attribute (available on images and links). But this approach doesn't allow us to use further mark-up, perhaps to show images or make use of internationalised text.
Some Ajax libraries go some way to addressing this problem, by placing the tooltip into a div element, and then using event handlers and JavaScript to make it visible at the right time. The problem with this is that although functionality is improved in comparison to HTML, the result is forms that are non-portable and non-accessible: they are non-portable, because not only are they dependent on script, but they are dependent on a particular script library; they are non-accessible because a voice browser would have no idea what to do with the div.
XForms provides powerful tooltip features by learning from both the HTML and the Ajax library techniques. XForms uses nice clean mark-up that hides any implementation details, just like the HTML title approach, but it uses an element rather than an attribute so that we can include other mark-up. The element is called hint, and can be used on any form control.
Let's add some hints to our form to help our users, starting with a simple one on the URL control:
<xf:input ref="url">
<xf:label>URL:</xf:label>
<xf:hint>Please enter the URL for the link you want to store</xf:hint>
</xf:input>
If you add this hint to the form so far, you should end up with something like this (when you've opened the form, move your mouse over the URL control to see the hint):
Next we'll add a hint to the description control, but this time we'll make use of the HTML strong element to emphasise to our users what it is exactly that we want them to enter:
<xf:input ref="description">
<xf:label>Description:</xf:label>
<xf:hint>Enter a <strong>description</strong> for the link</xf:hint>
</xf:input>(Note that you don't need to use a prefix on HTML tags in the way that we are doing for tags from the XForms language.)
The form will now look something like this:
You can use pretty much any HTML or XForms elements you like inside hint, making this a very easy way to create complicated user interfaces; for example, when a user hovers over the name of a person or a company, you could show the corresponding face or logo. Similarly, hovering over a product name might show its picture. In fact, you could even use the hint to show a further sub-form that collects more information from the user. Nesting mark-up in this way is a powerful technique which you will see used again and again, and we'll show some real examples in a later tutorial.
Whilst desktop applications nearly always have some form of help text available to guide the user, web applications generally do not. This is because there is no straightforward mechanism to provide help to a user in HTML; instead the author needs to produce correctly formatted help files, or register for specific keyboard events.
XForms provides the help element to fill this need.
As with hint, the help element can be added to any form control, can contain other mark-up, and hides its implementation details--all of which make it very easy for authors to use.
Let's use the help element to provide more information about the Description control:
<xf:input ref="description">
<xf:label>Description:</xf:label>
<xf:hint>Enter a <strong>description</strong> for the link</xf:hint>
<xf:help>
Although called a <em>description</em>, del.icio.us uses this
field more like a title.
</xf:help>
</xf:input>
Once you've updated your form, reloaded it and put your cursor in the Description field, press F1. The display you have should look something like this:
The user can continue to interact with the form whilst the help is displayed--it won't be removed until the OK button is pressed. The help window can be moved out of the way if necessary, simply by dragging the blue title bar.
Since you can place other mark-up into the help text, try adding the del.icio.us logo, or some links to external documents that might give your users even more information. Remember that in HTML, if you want your link to open in a new window, you need to set the target attribute to _new...otherwise you will lose your application!
Now that we know how to build a simple form and help the user with their data entry, our next step is to do something useful with their input. We'll now extend our simple two field form to save the data the user has entered--a URL and a description--to the del.icio.us social bookmarking system.
If you're not familiar with del.icio.us then you'll find a link below to a description, along with instructions for obtaining an account that you can use with this tutorial. If you already have an account, then go right ahead and add the submission details.
(If you want to get the completed source code, it's available here.)
Tutorial reviewed 2006-12-06, by MB.
If you haven't used del.icio.us before, it's an online link manager that allows you to save links to useful things on the web, along with a description (so you can remember what the link was about) and some tags (to help you group them). The great thing is that unlike the set of favourites that are stored by your browser and will only work with that specific browser, you can get to your del.icio.us links from anywhere.
(For some, the most interesting thing about del.icio.us is that all of your bookmarks are public. In other words, other people can see your links, and you can see theirs--what has been called social bookmarking.)
del.icio.us not only provides an extremely useful service, but it also has a non-browser based interface to that service; this type of arrangement is often called an API, and it means that we can build our own link manager using whatever tools we want, as long as we post the right values to the del.icio.us servers.
Since everyone posts to the same URLs, the del.icio.us API needs some way of telling which store to place the data in. It does this by requiring you to authenticate. This is handled for us by XForms, but if you want to follow on with this example, you'll need to create a del.icio.us account.
If you don't already have one, you can obtain an account by going to the del.icio.us registration page.
To save links to del.icio.us we need to make an HTTP GET request to the following URL:
https://api.del.icio.us/v1/posts/add
The minimum parameters required are the URL of the link to be added, and a description. For example:
https://api.del.icio.us/v1/posts/add?url=http://skimstone.x-port.net&description=The+skimstone+site.Other parameters can be added, and they include more detailed notes about the link, a space-separated list of tags, a timestamp, a flag to indicate whether to replace any pre-existing information (if there is any) and a flag to indicate whether to keep the link private.
Details of other commands that can be used with posts are at http://del.icio.us/help/api/posts.
The del.icio.us API supports all sorts of methods for adding, deleting and retrieving your links. The particular method that we are interested in for the moment is add, which allows us to post new links to the store.
Although called an API, the actual interaction we have with del.icio.us is via simple HTTP requests. To add a link to the del.icio.us store for example, we need to send the correct information to the following URL:
http://del.icio.us/api/posts/add
The specific parameters del.icio.us needs from us to store a link, are:
XForms will automatically create parameters in the request using the names that we use in the controls, so we made sure earlier to set the names in our input controls to be the same as for the del.icio.us add method:
<xf:input ref="url"> <xf:label>URL:</xf:label> </xf:input> <xf:input ref="description"> <xf:label>Description:</xf:label> </xf:input>
All we have to do now is define the end-point for the request--where we want this data sent to. For this we use the XForms submission element, which is part of the XForms model. (The model provides us with a lot of features beyond just submitting data, and we'll see more of these later.)
To set up the request, insert the following model and submission elements into the head element in your form:
</style>
<xf:model>
<xf:submission id="sub-add-link"
action="http://del.icio.us/api/posts/add" method="get"
/>
</xf:model>
</head>This gives us a request that will send the values in our two controls to del.icio.us, but we still need some way to kick off the submission.
We created a submission that will send the data the user has entered to del.icio.us, and we gave it an id value of sub-add-link. We can now create a submit button that refers to this named submission, and when the user clicks on it, the data in the form will be sent to del.icio.us, as follows:
<xf:submit submission="sub-add-link">
<xf:label>Save</xf:label>
</xf:submit>
</body>
</html>
When you reload your form you'll notice that the submit button has been added, but it looks a little odd:
This is because the text in the submit button comes from a label just like any other form control, and we defined a CSS rule such that all labels should be 12em wide:
xf\:label
{
width : 12em;
vertical-align : middle;
margin-right : 0.2em;
}
We can easily fix this by changing our CSS rule to be more specific; we'll make it apply only to labels that are children of input controls:
xf\:input xf\:label
{
width : 12em;
vertical-align : middle;
margin-right : 0.2em;
}
Let's improve our button a little, and add the del.icio.us logo. Edit the label on the submit element to include an image tag:
<xf:submit submission="sub-add-link">
<xf:label>
<img src="http://del.icio.us/static/img/delicious.gif" alt="" />
Save
</xf:label>
</xf:submit>
Save and refresh, and you should see a much nicer looking button:
We can now move on to test everything.
To test the form, get a URL for a document that you want to save for later. You could use a link to this tutorial:
http://www.formsPlayer.com/introduction-to-xforms
Enter this into the URL field, along with a brief description--something like:
An introduction to creating web applications with XForms.
Your form should now look like this, and be ready to send:
When you press Save you'll be asked to log in. If you have used del.icio.us before, then you may already be logged in, but if not you will get a prompt similar to this:
Once you've entered your information, you will see that the form is replaced with a simple success message to indicate that your link has been saved:
This is exactly how HTML forms work--the current page is replaced with a new one from the server--and is the default behaviour in XForms. However, we can do far more interesting things with submission, as we'll see later.
In the meantime, if you want to assure yourself that this worked, open your del.icio.us page in a new browser. You can find your page by making a URL from your login name and the del.icio.us home-page. For example, my page is:
http://del.icio.us/mark.birbeckIf you're happy that your link was saved, we can move on to add some more features to the form.
Many of the forms that we fill in on the web are part of processes that require very specific data. It may be our credit card details or the time of the return flight, but whatever it is, nearly every form we fill in will have values that are required for some operation to complete successfully.
HTML forms don't allow us to specify which form controls must be completed, so there are generally two approaches that are taken. The first is to let the server check for the values after the user has completed the form, and the second is to add some script to the form to check all the required data is present, and prevent sending the data if it isn't.
Of course any significant process on a server is going to have to check the values anyway, but if we don't check the values in the web browser we will have made an unnecessary round-trip to the server, and provided our users with a poor experience.
However, although the alternative of using script improves the user experience, it ultimately suffers from the same problem that we keep coming back to; it relies on a technique that is non-standard, and available only to programmers.
XForms solves this problem by making this functionality available to mark-up authors via a simple attribute that indicates that some particular piece of data is required. The attribute is called required and it sits in the model, on an element called bind.
We'll illustrate the use of this property by indicating in our form that the URL and Description controls must be filled in (i.e., the items called url and description are required data). Before we show how to add these rules, try pressing Save with either of the two controls empty, and you'll see that del.icio.us objects:
So, in order to prevent the submission if either URL or Description are empty, update the XForms model as follows:
<xf:model>
<xf:submission id="sub-add-link"
action="http://del.icio.us/api/posts/add" method="get"
/>
<xf:bind nodeset="url" required="true()" />
<xf:bind nodeset="description" required="true()" />
</xf:model>
Once you have added the above mark-up to your form, save and reload, and then try pressing the Save button without filling any data in. As long as one or other of the URL or Title controls are empty, nothing will happen, and you will never be able to post to del.icio.us.
Unfortunately, now if there is an error, nothing happens. In the next section we'll see how to tell our users that something has gone wrong.
Using the required property we have now been able to prevent any data being sent to del.icio.us if one or other of the obligatory fields is missing. However, as things stand the user of our form would be none the wiser as to what has gone wrong, and may even just keep pressing the Save button.
In HTML, the usual way to provide feedback to the user is to call the alert() function in script. This doesn't give us very rich messages though, so XForms solves this--in just the same ways as it did with hint and help which we discussed before--by providing the message element.
Let's see what a message that tells the user that they haven't entered enough information for the form to proceed, might look like:
<xf:message level="modal"> Please ensure that you have entered both a URL <em>and</em> a description. </xf:message>
The level attribute indicates what type of message we want, and in this case we're using a modal message, which halts processing until the user acknowledges it.
But when does this message get displayed to the user? As it stands, never. This is because the message element is one of a number of XForms handlers which will do a specific task, but will only do so when instructed to. However, we know when we want this action to be performed, and that is when there is an error on submission of the data; so to ensure that our message action is carried out we need to register for the notification that will be issued when a submission error occurs.
It's extremely important in both Ajax and desktop applications to know how the submission of data is progressing. In XForms, information is provided to us both when the submission begins, and again when it ends.
The way in which all notifications happen in XForms is via events. A process will dispatch an event to any listener that registered for it, and this will in turn execute whatever actions have been marked up.
In the example we've been looking at we want to register to be notified if ever the xforms-submit-error event occurs on the submission labelled sub-add-link. The action we want to execute when this event does occur is to show a message. That may all sound very complicated, but in fact the mark-up to do this is as straightforward as adding the message handler as a child of the submission element:
<xf:submission id="sub-add-link"
action="http://del.icio.us/api/posts/add" method="get"
>
<xf:message level="modal" ev:event="xforms-submit-error">
Please ensure that you have entered both a
URL <em>and</em> a title.
</xf:message>
</xf:submission>
Making the message action a child of submission ensures that we are registered on the correct element, but we still need to say what event we are registered for, since there are many possible notifications. That's the purpose of the ev:event attribute, which specifies the event we're interested in--in this case xforms-submit-error.
One more thing to complete the mark-up is that ev:event is actually from a different language to XForms. We therefore need to ensure that the namespace for this language is at the top of our document:
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xf="http://www.w3.org/2002/xforms"
>
.
.
.
Update your form to include this namespace definition and the changes to the submission element described above, and then reload. Now if you try to post to del.icio.us without any values in the URL or Description fields you should get something like this:
The XForms textarea control is used when the amount of text you want your users to enter is quite large, and you want to give them space to compose and manipulate their content. Typical uses would be content management systems, updating blogs, adding comments to a forum and so on. The control itself is used in the same way as the input control, so you can make use of features like hint and help.
In addition to the required fields url and description, the add method in the del.icio.us API has a number of optional parameters. Once of these is called extended, and allows the user to enter a much fuller description of the link.
Let's add a textarea for this, although we'll call it Description, and change the name of our current Description control to Title (it seems easier to understand than extended):
<xf:input ref="description">
<xf:label>Title:</xf:label>
<xf:hint>Enter a <strong>title</strong> for the link</xf:hint>
</xf:input>
<xf:textarea ref="extended">
<xf:label>Description:</xf:label>
<xf:hint>Enter a <strong>description</strong> for the link</xf:hint>
<xf:help>
As well as providing a title for your link, del.icio.us
allows you to add a full description.
</xf:help>
</xf:textarea>
(Note that we've also removed the help text from the description field, since 'Title' seems clear enough now.)
To fit in with the styling that we've been using--one control per row, a dotted line for the bottom border, etc.--you'll also need to add a CSS rule for textarea to the rules for input and output:
xf\:input,
xf\:textarea,
xf\:output
{
.
.
.
Similarly, we'll need a rule for textarea labels:
xf\:input xf\:label,
xf\:textarea xf\:label
{
.
.
.
and finally, one to set the data-entry area of the textarea:
.input-value,
.textarea-value
{
.
.
.If you reload your form, it should now look something like this:
In our del.icio.us form we could make life easier for our users if we didn't show the Title control until the URL field was completed, and similarly we didn't show the Description control until both the URL and Title were filled in. We could also use the same rules to hide the submit button until there was enough data present to make submission possible.
This is possible in XForms by defining rules that say when a control should be relevant or not; it might be that certain fields are only relevant if someone is self-employed or the property they want to buy is a flat. By defining such rules, the controls on the form are revealed in stages as the user fills data in, making errors less likely, and improving the user experience. Let's see how this is done.
We saw earlier how we can use simple rules to indicate that some data values are required, which allowed us to prevent submission unless the values were present:
<xf:model>
.
.
.
<xf:bind nodeset="url" required="true()" />
<xf:bind nodeset="description" required="true()" />
</xf:model>A similar technique is used to say when a form control is relevant or not. For example, if the user indicates that they only want to buy a single train ticket, a rule can be set that stops the return date control from being shown.
To make the relevance of the description field dependent on the presence of a value in the URL item, add the following rule to the bind statements:
<xf:bind nodeset="url" required="true()" />
<xf:bind nodeset="description" required="true()" relevant="../url != ''" />
</xf:model>
This simply says that the item description is only relevant if some data has been entered in the item url--i.e., url is not equal to the empty string.
Note the "../" in front of the reference to url; just as it does when changing directories on your disk-drive, ".." means 'go up a level'. We need to do this because the expressions used in @relevant and @required are calculated relative to the value inside @nodeset; since url sits on the same level as description, then to get to it we must go 'up' from description and then come back down again to get to url.
(We'll go into expressions in more detail in a later tutorial, but the long and the short of it is that if we just used the expression url without "../" then we would be making a reference to some data that was underneath the item description, rather than to data at the same level.)
The rule for extended is slightly longer, since we'll make that depend on both the url and description items having data:
<xf:bind nodeset="url" required="true()" />
<xf:bind nodeset="description" required="true()" relevant="../url != ''" />
<xf:bind nodeset="extended" relevant="../url != '' and ../description != ''" />
</xf:model>
XForms also allows us to tell our submit button to adopt the relevance properties of some named item. This is useful, since without this we wouldn't be able to show and hide buttons in the same way that we do other controls.
The rule we just created for extended--showing and hiding based on whether url and description have data in them--is ideal for the button that sends the data to del.icio.us, and we can use it as follows:
<xf:submit submission="sub-add-link" ref="extended">
<xf:label>Search</xf:label>
</xf:submit>
Now the button will receive all of the same 'special' properties that the Extended control gets.
The result of the relevance rules that we've created is not actually to show and hide the controls, but rather to set a CSS pseudo-class on the controls. The pseudo-classes are :disabled and :enabled.
To hide all of our form controls when they are non-relevant (when their CSS pseudo-class is :disabled) we don't actually need to do anything, since the default CSS rule is:
.pc-disabled
{
display : none;
}
Since this is formsPlayer's default we don't need to specify it in our stylesheets, but we're not limited just to showing and hiding controls, and we can use any CSS features. For example, if we would like to apply a yellow fade animation to controls as they become relevant, we can add the following to the list of style rules:
.pc-enabled
{
-event-xforms-enabled : fx-Effect-Highlight();
}
</style>
If you save and refresh your form, you will now see this:
Now, to see how relevance works, enter something in the URL control. If you don't have anything convenient, the URL for this handbook is:
http://skimstone.x-port.net/introduction-to-xforms
After you tab away from the control you should see the Title field become available, and it has a yellow background to draw attention to the appearance of the new control:
The yellow background quickly fades away:
Now add a title. If you are saving a bookmark for this handbook, then the title is:
An introduction to creating web applications with XForms.
This time, after you tab away both the Description control and the Save button should become visible:
If you're saving a bookmark to this tutorial, your previous entry will be updated. Paste the following to the Description field:
XForms is an exciting new language from the W3C that can be used to create anything from simple forms to complex Web 2.0 applications. XForms are dynamic, cross-platform, accessible, script-free...and 100% standard. This handbook covers everything you need to know to get started with XForms.
Your form should now look something like this, and you can go ahead and save the link:
Another piece of information that del.icio.us can store for us is a date value. We'll add a control for this to our form.
The first thing we need to do is add a new input control after the Description field, and before the Save button:
</xf:textarea>
<xf:input ref="dt">
<xf:label>Date:</xf:label>
<xf:hint>Enter a date to store with this link</xf:hint>
</xf:input>
<xf:submit submission="sub-add-link" ref="extended">
However, if you save and refresh you won't see anything remotely date-related--all you'll get is an ordinary input control. But to get a control that is geared towards accepting dates--a calendar widget--we don't change the control, but the data.
XForms makes extensive use of a model-view-controller (MVC) architecture. There's a lot written on the subject of MVC, and to be honest there's also a lot of disagreement as to the details. But for our purposes the main thing to understand is that it is a 'good thing' if the user interface changes the way it behaves depending on the data it is showing.
XForms does exactly this--if the item referenced by an input control is a date then we get a calendar widget, and if it is a boolean we get a check box. We don't need to change the controls--we just use a normal input--and everything else is done for us. This makes our applications cleaner and easier to maintain.
To indicate that dt is a date, place the following at the end of your current list of bind statements:
<xf:bind nodeset="extended" relevant="../url != '' and ../description != ''" />
<xf:bind nodeset="dt" type="xs:date" />
</xf:model>
The type attribute is used to indicate the type of the data, and since the W3C's XML Schema specification already has definitions for data types like numbers and dates, XForms makes use of it. However, since it is prefixed with xsd we need to ensure that the namespace mapping is at the top of our document:
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
.
.
.
If you make these three changes--add the namespace declaration, the bind statement and the additional input control--and then save and reload the form, you will have something like this:
Note that the Date control is visible because we haven't added a relevant rule to hide it, yet. When you've had a good look at the calendar widget and seen what it can do, make a copy of the relevant rule for extended, and use it for the dt node:
<xf:bind nodeset="extended"
relevant="../url != '' and ../description != ''" />
<xf:bind nodeset="dt" type="xs:date" relevant="../url != '' and ../description != ''" />
</xf:model>
Once data is entered in the URL and Title fields, the other controls will be revealed:
XForms provides two separate controls for selecting items from a list. The first is select, which presents a list to the user and allows them to choose as many items as they like, whilst the second is select1 which limits the user to one selection.
The del.icio.us add method has an optional parameter called replace which indicates whether the data being submitted should overwrite any existing data. If the value is anything but no then the data will be overwritten. We can provide the user with a way to set this value by using a select1 control.
The select1 control has all of the same features that an input has--such as hint, help, and so on--but in addition supports the item element to provide choices that the user is able to select from. For the del.icio.us form we need two choices, "yes" and "no".
The following mark-up gives us a select1 with these two choices, and a hint to help the user. Add it to your form, just before the submit button:
</xf:input>
<xf:select1 ref="replace">
<xf:label>Replace:</xf:label>
<xf:hint>
Set this to <em>no</em> to prevent
del.icio.us from overwriting a previous entry
for the same link, if one exists.
</xf:hint>
<xf:item>
<xf:label>Yes</xf:label>
<xf:value>yes</xf:value>
</xf:item>
<xf:item>
<xf:label>No</xf:label>
<xf:value>no</xf:value>
</xf:item>
</xf:select1>
<xf:submit submission="sub-add-link" ref="extended">
You'll also need to add the same relevance rule that we use for extended and dt, so that the control is hidden until a URL and a title have been entered:
<xf:bind nodeset="extended" relevant="../url != '' and ../description != ''" />
<xf:bind nodeset="dt" relevant="../url != '' and ../description != ''" />
<xf:bind nodeset="replace" relevant="../url != '' and ../description != ''" />
<xf:bind nodeset="dt" type="xsd:date" />
</xf:model>
And finally, to get the formatting right, you'll also want to add to the CSS rules:
xf\:input,
xf\:select1,
xf\:textarea,
xf\:output
{
.
.
.
}
.input-value,
.select1-value,
.textarea-value
{
.
.
.
}
xf\:input xf\:label,
xf\:select1 xf\:label,
xf\:textarea xf\:label
{
.
.
.
}
If you save the form and then refresh it in the browser, your form should look something like this:
The default display of the select1 that we just added was a drop-box. Howevever, often when there are only a couple of choices we would rather have them displayed as radio buttons. This is easily done in formsPlayer by adding the appearance attribute and giving it a value of full:
<xf:select1 ref="replace" appearance="full">
<xf:label>Replace:</xf:label>
.
.
.
</xf:select1>Just to be clear, this attribute doesn't mean 'please give me radio buttons'--instead it informs an XForms processor that the author would like as many of the choices in the list to be available to the user as possible. Of course, how that actually pans out and gets implemented will be different on different systems; the effect of this on a voice-based XForms processor will be different to the effect on a GUI-based one, and different again to a mobile phone.
But the idea is that there is at least some similarity in the way this is implemented, and the author is in effect providing a 'hint' to the processor about what they would like to happen. In the case of formsPlayer, when appearance is set to full on a select1 then all of the options in the list are made available via radio buttons:
Recall that the replace item is used to tell the del.icio.us servers whether it should overwrite any previous entry for the link we are submitting. We created a couple of radio buttons to allow us to set this value, but if you ran the form, or looked carefully at the screenshot, you might have noticed that none of the radio buttons had an initial value.
As it happens this is fine for del.icio.us, since it doesn't actually need this value--replace is only significant if it is present and set to "no". Still, so that we have something that we can use to illustrate the points, we'll pretend that the value of replace is important, and that we need to set a default value of "yes".
To set the value of an item in XForms, we use an action handler, called setvalue. (You met 'action handlers' before, when we looked at message.) The handler takes the ref attribute to indicate what item should have its value set, and the content of the element indicates the value that the item will be set to. In our case we want to set the value of replace to "yes", so we'll do this:
<xf:setvalue ref="replace">yes</xf:setvalue>
As with the message handler, this action won't actually do anything until it gets a signal. In the message example the 'signal' we chose to register for was the notification event that is given whenever there is a submission error. For our setvalue we'll also register for a notification event, and this time it's xforms-ready which is fired by an XForms processor once initialisation is complete. Place the following mark-up just before the closing tag of the model element:
<xf:bind nodeset="dt" type="xsd:date" />
<xf:setvalue ev:event="xforms-ready" ref="replace">yes</xf:setvalue>
</xf:model>
Once again, save and reload and your form, and notice that the Replace control is initialised to "Yes".
Whilst the select1 control allows the user to choose one option from amongst many, the select control allows multiple options to be picked. We'll illustrate the use of this control by providing a way for users to indicate the subject of the link. To begin with we'll assume that any links we save are about XForms, skiing or both:
</xf:textarea>
<xf:select ref="tags" appearance="full">
<xf:label>Tags:</xf:label>
<xf:hint>Enter some <em>tags</em> for the link</xf:hint>
<xf:item>
<xf:label>XForms</xf:label>
<xf:value>xforms</xf:value>
</xf:item>
<xf:item>
<xf:label>Skiing</xf:label>
<xf:value>skiing</xf:value>
</xf:item>
</xf:select>
<xf:input ref="dt">
As with the other controls we'll only allow the user to enter data into this one when a URL and title have been provided:
<xf:bind nodeset="extended" relevant="../url != '' and ../description != ''" />
<xf:bind nodeset="tags" relevant="../url != '' and ../description != ''" />
<xf:bind nodeset="dt" type="xs:date" relevant="../url != '' and ../description != ''" />
Finally, we need to update the CSS rules:
xf\:input,
xf\:select,
xf\:select1,
xf\:textarea,
xf\:output
{
.
.
.
}
.input-value,
.select-value,
.select1-value,
.textarea-value
{
.
.
.
}
xf\:input xf\:label,
xf\:select xf\:label,
xf\:select1 xf\:label,
xf\:textarea xf\:label
{
.
.
.
}
Save the form, refresh your browser, and then enter a URL and a title, and when the hidden controls are revealed you should see two check-boxes for adding tags.
Flickr is well known both as a full-featured image storage system, and one of the web applications that got everyone talking about Web 2.0 and Ajax.
In the following section we'll make use of the powerful Flickr API to search for a list of photos that have a particular tag on them. We'll also demonstrate how to show and hide parts of the application based on its state, as well as how to animate the display when these states change.
(If you want to get the completed source code, it's available here.)
Tutorial reviewed 2006-12-06, by MB.
The first thing we need to do is create an empty XHTML document into which we can place the mark-up for the Flickr seach form. If you created the template at the beginning of the Introduction to XForms then go ahead and use that. Otherwise, create a new document in your editor and copy the template from here. Change the title to "Flickr Search Form", and save the file as flickr.html.
The finished form also requires at least version 1.4.2.1016 of formsPlayer, so if necessary, change the object tag, as follows:
<head>
<object width="0" height="0" id="formsPlayer"
classid="CLSID:4D0ABA11-C5F0-4478-991A-375C4B648F58"
codebase="http://skimstone.x-port.net/files/releases/formsPlayer-1.4.2.1016.cab#Version=1,4,2,1016"
>
<b>formsPlayer has not been installed.</b>
</object>
We'll also create a separate CSS file to hold our style rules so add the following line to the head:
<title>Flickr Search Form</title>
<link rel="stylesheet" href="flickr.css" type="text/css" />
</head>
And then create an empty file, called flickr.css. We're ready to go.
The application is going to be made up of two parts. The first is a small form that will allow a user to enter a search term and then press a button to perform the search.
The second part will contain a repeating structure that will automatically take each photo element returned from the search, and uses it to create a reference to the actual image on the Flickr servers.
To create the search form, add the following to the body element:
<body>
<xf:group id="main-body">
<xf:input ref="tags">
<xf:label>Tags:</xf:label>
</xf:input>
<xf:submit>
<xf:label>Find</xf:label>
</xf:submit>
</xf:group>
</body>
This gives us a simple input control with a button next to it. We've placed the two controls inside a group mainly to make styling easier. Add the following style rules to your flickr.css file:
body
{
font-family : "Trebuchet MS", Verdana, Helvetica, Sans-Serif;
font-size : 11px;
}
#main-body
{
margin : 0;
background-color : #eee;
border : 5px solid #333;
padding : 15px;
display : block;
}
Save both of your files and load flickr.html in the browser, and you should have something like this:
Flickr provides a number of different services for interacting with its data. If you look at the list of methods that the Flickr API supports, you'll see they are divided into groups; one group of API methods deals with photos, another with people, and so on.
Flickr can accept requests to these methods in a number of different formats, all of which we can use with XForms. The easiest to understand and test is REST, and to use it to make a call to any one of the methods listed we need to go through the following URI:
http://www.flickr.com/services/rest/
The parameters that you must pass are the method you want to call, and a special application key. The key must be obtained from Flickr, and although we have one for this tutorial, if you plan to develop the sample form further you should get your own key.
The particular method we will use for this form is flickr.photos.search, and you can see from the documentation that this method takes a number of parameters. Of the optional parameters, the only ones that we'll use are tags and per_page.
The tags parameter contains the tag or tags that the user of our form wants to search for, whilst per_page indicates how many items 'per page' we would like to be returned; since there may be tens of thousands of items that match a particular tag, Flickr will give the list to us one page at a time, and this parameter indicates how many items we would like on each of those pages.
Combining these two method-specific parameters with our general ones, and using the 'REST URL', we have a resulting call to the method that looks like this (the extra line breaks are to make it easier to read):
http://www.flickr.com/services/rest/? method=flickr.photos.search& api_key=68149024a667e0be3c63708f002ffe1e& tags=dogs& per_page=12
The results of such a call might be something like this (try it):
<rsp stat="ok">
<photos page="1" pages="983" perpage="24" total="98265">
<photo id="94048829" owner="54228211@N00" secret="9b2e52936b"
server="37" title="These paws are made for walking - 5997"
ispublic="1" isfriend="0" isfamily="0"
/>
<photo id="94044468" owner="23819962@N00" secret="1641bc41c5"
server="41" title="listen up dogs"
ispublic="1" isfriend="0" isfamily="0"
/>
.
.
.
</photos>
</rsp>
All data from Flickr is structured in much the same way as this; the containing element is always rsp, and there is always a stat attribute that indicates whether the request was successful or not.
What is inside the rsp element will change from method to method, but if the result is a set of photos, then they will always be in a list like this. Note that there is actually no URL for the image itself in the set of results, since it is our job to create it by combining together the information passed to us in the photo element. This is because Flickr stores multiple formats of the same image, and we'll see how to combine the pieces later.
In our del.icio.us form we saw that all of the parameters we needed had a corresponding form control. We could do the same here, but it would mean creating a form control for things like the method and api_key which are not pieces of information that we want our users to be able to update. To create a nice easy to use form for searching Flickr, we only really need one form control, and that is for the tags value; all the other fields can be given fixed values.
To achieve this we need to create an XForms data island that will hold all of our items. This may seem a little different to how we worked with the del.icio.us example, but in fact it isn't--all that happened with the del.icio.us form was that formsPlayer created a 'default' data island for us since we didn't specify one explicitly. And it used the form controls that we had provided to 'work out' how the 'automatic' data island should be structured. (This is called 'lazy authoring', for obvious reaons.)
Let's create a data island that contains the items we need for our search on Flickr (instances are placed inside an XForms model):
<link rel="stylesheet" href="flickr.css" type="text/css" />
<xf:model>
<xf:instance>
<instanceData xmlns="">
<method>flickr.photos.search</method>
<api_key>68149024a667e0be3c63708f002ffe1e</api_key>
<tags />
<per_page>12</per_page>
</instanceData>
</xf:instance>
</xf:model>
</head>
Hopefully this all looks familiar to you, since all we have created is one XML item for each of the parameters that Flickr needs, and given each a value should it need one. Note that we've also created an empty entry for tags, even though we have a form control that refers to it. This is because the automatic creation of 'default' items only happens if we don't provide an instance; but once you do create one, then you will have to include within it any items that you need for your controls. However, the input control that we created earlier will correctly reference the tags item in this instance, just as before it was referencing the one that was created automatically.
Now that we have set up a data island ready to send to Flickr, we can go on to prepare the request that will be used to indicate where to send everything. You saw earlier how to specify a request to del.icio.us, and we need to do much the same here:
</xf:instance>
<xf:submission id="sub-flickr"
method="get" action="http://www.flickr.com/services/rest/"
separator="&"
/>
</xf:model>
The only difference you will see is that we had to explicitly say that the separator value is "&", since with XForms the default is ";". (We didn't bother to do this for del.icio.us, since their servers understand both values.)
Save the form, refresh, type a value into the input control, and press the "Find" button, and you should get something like this:
We mentioned in an earlier section that replacing the form with the data returned from the server was the default behaviour for XForms, making it exactly the same as HTML forms. In the next section we'll show how to make use of the XML without replacing the entire form, but before we do, go 'back' in the browser to your form, and try pressing "Find" again without entering a tag; note that this time you should get an error message from Flickr:
The whole point of using the Flickr API is so that we can make use of the XML returned. Unlike HTML forms where the page will be replaced by whatever comes back from the server, XForms allows us to retrieve data from a server, store it locally and then manipulate it.
In order to manage the parameters that were to be sent to Flickr we created an instance in the model. We'll do the same now to manage the data that is returned from Flickr:
</xf:instance>
<xf:instance id="inst-rs">
<dummy xmlns="" />
</xf:instance>
<xf:submission id="sub-flickr"
We've given the instance a dummy value since it will be replaced anyway by whatever comes back from Flickr.
Now we can modify the request so that instead of replacing the entire form--the default behaviour--the returned data is placed into our new instance. This is easily done by adding the following two attributes to the submission:
<xf:submission id="sub-flickr"
method="get" action="http://www.flickr.com/services/rest/"
separator="&"
replace="instance" instance="inst-rs"
/>
(Note that the default value for replace is all, which is the 'HTML form'-style behaviour that we have been seeing, where the entire form is replaced with whatever the server returns.)
If you refresh your form and try searching again it appears that nothing has happened! This is because the data from Flickr is now being stored in the instance inst-rs, and the form is no longer being replaced. The next step is to do something with the data.
The output control has much the same structure as the input control, which means we can add labels and hints for example. But as you'd probably guess, it's not an interactive control--it's just used to get some data out of the instance so that the user can see it.
We'll use an output control as a simple way to show any error text that comes back from Flickr. Add the following just after the submit button:
</xf:submit>
<xf:output ref="instance('inst-rs')/err/@msg">
<xf:label>Error:</xf:label>
</xf:output>
</xf:group>
Note that we have to use the instance function since there is more than one instance our form--one for specifying the parameters to send, and one to hold the response from Flickr--and formsPlayer will always use the first one unless told to do otherwise.
You'll no doubt be wondering what is going to happen here if there is no error string returned from Flickr. The answer is that in XForms if you add a form control and connect it to an item that does not exist, then by default it is regarded as non-relevant--and as we saw earlier, relevance is used to show and hide controls, so our control will simply not appear unless there is an error message.
(Note that if you don't create an instance then items will never 'not exist' since formsPlayer will create a set of default items for you. If you are not clear on why this is, you might want to revisit the earlier discussion about 'lazy authoring'.)
In our example, our output form control will automatically be given the CSS pseudo-class :disabled if there is no error (i.e., the item err/@msg does not exist), and it will have the class :enabled if there is an error. This is a very simple and convenient way to have the conditional display of items, and it means that we can specify CSS style rules that take advantage of these automatic class changes. In this case we don't need anything, since the default style rule for :disabled is display: none;.
If you reload your form and press "Find" with nothing in the Tags: control, you should get the following:
Just to spell out what is happening, Flickr is giving us back exactly the same data that you saw before, but now instead of wiping out our form, formsPlayer is storing the returned XML in a data island. An output control is 'wired up' to show a specific item in the data island if it exists, and if it doesn't, the control is hidden.
A repeating structure usually involves defining a list of items to 'repeat over' (or iterate) and a template to be applied to each item in the list.
In many systems the template part would need to be defined with a separate file, and although there will be certain situations where this is a good approach, there are also plenty of situations where it's just that bit too much work for the simple job in hand.
The XForms repeat element allows us to specify both the list and template in one. The items that make up the list are expressed using an XPath expression, and the template is defined by placing mark-up inside repeat. (The mark-up can be anything, including further repeat elements.)
For the Flickr form we want a repeating structure that has as many entries as there are photo items in the returned XML. The XPath to create a list of photo items is:
instance('inst-rs')/photos/photo
We can use this expression to set up a repeating structure that operates on each returned photo, by adding the following to the form just after the output that shows any error results:
</xf:output>
<xf:repeat nodeset="instance('inst-rs')/photos/photo">
[...]
</xf:repeat>
</xf:group>The template is created from any tags that are inside the repeat element itself, and can include any HTML and XForms tags, including the repeat element. A moment ago we set the template to a simple ellipsis:
<xf:repeat nodeset="instance('inst-rs')/photos/photo">
[...]
</xf:repeat>This will give us enough to test with, so save and refresh the form, and try searching for something--you should see as many ellipses as there are search results:
Now that we know that both the search and our repeat are working fine, let's fill out the repeat template.
We'll first use a simple output control to show the title of each of the photos returned from Flickr, before we then move on to show how to display the actual image.
The XML item that holds this data is the title attribute on each photo element. The XPath expression needed to refer to, say, the title of the third photo would be:
instance('inst-rs')/photos/photo[3]/@titleHowever, when we place controls into a repeat template, their XPath expressions are evaluated one at a time, in the context of each of the items in the list. So to show the title of each of the photos in the list, we need only do this:
<xf:repeat nodeset="instance('inst-rs')/photos/photo">
[<xf:output ref="@title" />]
</xf:repeat>Refresh the form, and try searching for something--you should see a list of image titles:
Flickr doesn't give us a URL for each photo since it stores many different formats of posted images. Instead, when we search, Flickr provides us with all the bits we need to create the URL of the image we want, using a set of predefined rules. This allows us to choose from a number of image formats, depending on the context.
To display each image, we therefore need to calculate the URLs and then make use of the URL to create an image in the form. We'll now show how to do that.
The first thing we need to do is create the full URL for each image. The URL consists of the Flickr domain name, a number for a server to retrieve the image from, an ID, a 'secret number', and a size:
http://static.flickr.com/{server}/{id}_{secret}_{size}.jpg
The following mark-up creates a URL like this for each photo in the search results:
<xf:repeat nodeset="instance('inst-rs')/photos/photo">
<xf:output
value="concat('
'http://static.flickr.com/',
@server, '/',
@id, '_',
@secret, '_s.jpg'
)"
/>
</xf:repeat>
The main new feature to note is that we're using the attribute value on our output control, rather than the ref attribute. The distinction is that ref refers to an item in an instance, whilst value can be any calculated expression.
In the value attribute we have an XPath expression that is making use of the concat function. All this function does is to join together all of its arguments to produce a string:
concat( 'http://static.flickr.com/', @server, '/', @id, '_', @secret, '_s.jpg' )
This is an XPath function that combines each of its parameters together to create a string. If any of the parameters are themselves XPath expressions then they are evaluated first, so in this case the values for the attributes server, id and secret are obtained from each photo and placed into the result.
Now that the output control has access to the calculated URL, we can make use of it to load the desired image. To do this, all we need to do is add the mediatype attribute which indicates that the data in the output is not just to be used as plain text for display, but is a reference to some other kind of object. The values of @mediatype will usually be MIME types, so to indicate that we want an image we simply use image/jpeg or image/*.
<xf:repeat nodeset="instance('inst-rs')/photos/photo">
<xf:output
value="concat('
'http://static.flickr.com/',
@server, '/',
@id, '_',
@secret, '_s.jpg'
)"
mediatype="image/*"
class="image"
/>
</xf:repeat>
You'll see that we also added a @class whilst we're here; this is so that we can make the images a little neater by adding the following style rule to the end of flickr.css:
xf\:output.image
{
border : 1px solid black;
width : 75px;
height : 75px;
margin : 5px;
}
Note the use of the class "image" that we set on the output; this is so that other uses of output don't get displayed as 75x75 squares.
Now, save, refresh and search, and you should have results that look something like this:
Our form is working fine so far, but we can improve on the user experience by having different parts of the form active at different stages.
For example, once a search is underway it would be nice to hide any previous images or error messages, and instead show some kind of indication that the search is 'in progress'. We can do this by making use of switch and case.
The switch element provides a container for a set of cases, only one of which will be active at any one time. We'll see exactly how to select a particular case in a moment, but first add the following mark-up after the submit button:
</xf:submit>
<xf:switch>
<xf:case id="case-start">Start</xf:case>
<xf:case id="case-busy">Busy</xf:case>
<xf:case id="case-done">Done</xf:case>
</xf:switch>
<xf:output ref="instance('inst-rs')/err/@msg">
If you reload your form you will see the word "Start" just below the input control, with "Busy" and "Error" hidden because only one case can be active at a time.
The next step is to change the active case as the user interacts with the application. The 'action' to do this is called toggle and it takes a single value, the id of the case to make active:
<xf:toggle case="case-busy" />
You might be tempted to think that the 'busy' case should be made active when the user clicks on the button, and this is indeed what many non-MVC systems would do. But XForms goes to great lengths to decouple the data and the user interface, and it is much better to change the active case to 'busy' when the submission begins, as indicated by the xforms-submit event.
By doing this we tie our 'busy' state to the request to Flickr, rather than some action performed by the user, and it's good to get into this habit; more advanced tutorials will show how to send data to a server as a result of other activities in the form, not just mouse clicks.
Just as we're going to use the xforms-submit event to select the 'busy' state, we'll use the xforms-submit-done event (dispatched when submission has completed successfully) to select the 'done' state. Modify the submission element to include our two action handlers:
<xf:submission id="sub-flickr"
method="get" action="http://www.flickr.com/services/rest/"
separator="&"
replace="instance" instance="inst-rs"
>
<xf:toggle case="case-busy" ev:event="xforms-submit" />
<xf:toggle case="case-done" ev:event="xforms-submit-done" />
</xf:submission>Save your changes and refresh your form, and you should see "Start" below the input control. Then each time you search you should see "Busy" as the search begins, and "Done" when it completes.
Our next step is to show an image to the user to indicate that the search is in progress. Of course you can put any mark-up you like in the "Busy" case, but we're going to show an animated image to give the user a sense of something happening. An image is attached to this page, and you will need to copy it to the same directory as your form. Then replace the text in the case with an img tag:
<xf:case id="case-busy">
<img src="spinner.gif" alt="Busy" />
</xf:case>Since we've already configured this case to be toggled when a submission begins, the user will see the animated image the moment they press the "Find" button.
Next we'll move the error message and repeat into the "Done" case. However, as we do we'll make a slight modification; you probably noticed that the XPath expressions in output and repeat both begin with a call to the instance function:
<xf:output ref="instance('inst-rs')/err/@msg"> <xf:label>Error:</xf:label> </xf:output> <xf:repeat nodeset="instance('inst-rs')/photos/photo"> ... </xf:repeat>
You may have wondered if it was possible to factor this out, and set a common context for both expressions, and it is.
Although groups can be used simply to establish a common area for a set of controls, another use for them is to set an evaluation context for a whole set of XPath expressions. This makes them not only easier to type but also to maintain, since if the data source changes it is easier to move controls around if their expressions are shorter.
For example, if you had an address item that had further items nested underneath, for street, city, country, and so on, you could set the context to be the address item, and then the expressions for each of the contained controls would be shorter.
To illustrate this, as we move the controls into the "Done" case we'll add a group to set context:
<xf:case id="case-done">
<xf:group ref="instance('inst-rs')">
<xf:output ref="err/@msg">
<xf:label>Error:</xf:label>
</xf:output>
<xf:repeat nodeset="photos/photo">
<xf:output
value="concat('
<img src="http://static.flickr.com/',
@server, '/',
@id, '_',
@secret, '_s.jpg" />'
)"
class="image"
/>
</xf:repeat>
</xf:group>
</xf:case>Setting the context won't have any effect on the way the form works, but moving everything into the "Done" case has; if you reload your saved form and click on "Find", any previous search results will be hidden, replaced by the 'in progress' animation, and once the search is complete, the "Done" state will be toggled in (showing either the images or an error message).
The next step is to have some kind of animation when the images are ready to be viewed, such as the whole panel sliding into view.
We can achieve this easily by adding a CSS rule that tells formsPlayer to slide down a case when it receives the xforms-select event.
the following to the end of flickr.css:
xf\:case
{
-event-xforms-select : fx-Effect-SlideDown(duration:1);
}
Note that there is a bug in Internet Explorer which gives us a slight flicker before the animation begins. We can work around this by overriding the default rule that hides deselected cases, and replacing it with a rule that performs a slide up on receiving the event xforms-deselect. The resulting CSS is:
xf\:case
{
-event-xforms-deselect : fx-Effect-SlideUp(duration:0);
-event-xforms-select : fx-Effect-SlideDown(duration:1);
}
Now, whenever an XForms case element receives the xforms-select event, a slide-down animation will be initiated, set to take 1 second. Similarly, whenever the xforms-deselect event occurs, a slide-up animation will be initiated.
Save the form, refresh and try searching; once the results are available they should slide into view, and each time you try a new search the previous results should be immediately hidden, before the new ones slide in again.
The range control is the XForms equivalent of a 'slider'. The XForms version allows us to indicate start, end and step values. We'll use a slider to allow the user to control the size of the images that are displayed.
The first step is to create some instance data to hold the current size of an image. In our applications we often have an instance called control that we use to hold assorted values like this, so add the following to the model:
</xf:submission>
<xf:instance id="inst-control">
<instanceData>
<size>75</size>
</instanceData>
</xf:instance>
</xf:model>
This gives us an element called size with an initial value of 75. We use this to start us off, since it is the size of the smaller images on Flickr that we have been using so far.
Next we add our range control. As with the other controls you have seen, we must have a label, but we can also optionally have help, hints, and so on. The range control supports start and end attributes to indicate the 'range' of possible values. We'll use 1 to 200, so as to allow our images to go from 1px up to 200px:
</xf:submit>
<xf:range
ref="instance('inst-control')/size"
start="1" end="200"
style="width: 200px;">
<xf:label>Size:</xf:label>
</xf:range>
<xf:switch>
The range control can only be used on data that is typed to be an integer. We saw how to set the data type of a node in the del.icio.us form when we set an element to be a date, and we will use the same technique here. Add a bind statement to the model, below our control instance:
</xf:instance>
<xf:bind nodeset="instance('inst-control')/size" type="xs:integer" />
</xf:model>
If you play with the form so far, you will have noticed that the images only resize after you have finished dragging the slider. [This part of the tutorial is being changed. In a previous version you could resize the images with the range control, but the technique to achieve this was non-standard. This is being changed.] This is fine for many situations, but doesn't feel quite right for tasks relating to the images, since more often than not you will drag the slider until the images look right, rather than paying attention to the value.
We can easily make our application resize the images as you drag by using the incremental attribute. This tells formsPlayer to periodically update the data value in size, rather than waiting until the user has finished dragging. The same technique can be used in any other form controls; you might use it in an input control to provide 'suggestions' to the user as they type, for example.
The attribute is set as follows:
<xf:range
ref="instance('inst-control')/scale"
incremental="true"
start="1" end="200"
style="width: 200px;">
<xf:label>Size:</xf:label>
</xf:range>
Now try dragging the control.
We saw ealier with the use of concat() how expressions can contain calls to functions. Another very useful function is if() which works much like it does in spreadsheets; if the first value is true then the second value is returned, otherwise the third value is returned.
We'll make use of this in the part of our application that works out the URL for the photo. Recall that we created the URL of the photo by joining various parts of the data that Flickr provided us with. One of these parts is a single letter to indicate what size of image we would like, with the choices being s, m and l. Let's avoid some of the distortion we see when we make the images too large by using the medium-sized image if the user takes the size over 100px:
<xf:output
value="concat(
'http://static.flickr.com/',
@server, '/',
@id, '_',
@secret,
'_',
if(
instance('inst-control')/size < 100,
's',
'm'
),
'.jpg',
'/>'
)"
class="image"
/>
Even though this introduction barely scratched the surface of what the combination of XForms, XHTML and CSS has to offer, you've already learned enough to build complex user interfaces.
Other tutorials will show how to build standalone desktop applications, browser extensions, and more.
If you'd like to read more introductory material before continuing with this book, try some of these:
XForms contains a number of different action handlers that can be used to perform tasks such as navigating to a new document, toggling a case, and so on.
formsPlayer 1.5.7 sees the introduction of new features to allow form authors to decide when the contents of a case element will load. This gives the form author finer control over the performance of an XForms document, by allowing them to choose whether to load some content at startup, or later on in the lifetime of a form instance. Until now, in 1.5 builds, the markup within a case element has been ignored until that case is first selected, giving potentially faster load times, but a slower reaction to the toggle action.
There are three new ways to ensure that a case element is ready prior to toggling into that case.
A case can now be preloaded through script- This could be actuated through some user interaction, or an event such as xforms-submit, to prepare the case for when it is needed.
<a href="#" onclick="document.getElementById('c1').loadContent();">Preload xf:case[@id="c1"] using some script</a>
If a switch contains a case which is almost always used, but is not the default case, and other cases are rarely used, that case can be loaded at startup thus:
<xf:case appearance="full"> ... </xf:case>
If all cases within a switch are likely to be used during a visit to the form, then the contents of all cases can be loaded at startup, by setting the appearance of the switch to full, thus:
<xf:switch appearance="full"> ... </xf:switch>
XForms provides a number of action handlers that can add and remove nodes in the instance data, as well as change the values of nodes. It's also possible to gain access to the instance data via an XML DOM so that values can be changed through script, if necessary.
There are a number of ways to insert and delete nodes in XForms, based around the action handlers insert and delete.
insert allows a node to be inserted into a nodeset, at a certain position. The handler has a set of default behaviours defined which creates for a very powerful and flexible action.
All of the following examples assume this initial instance data:
<instanceData xmlns="">
<list>
<y>y1</y>
<y>y2</y>
<y>y3</y>
</list>
<templates>
<x>x1</x>
</templates>
</instanceData>
The simplest form of the handler specifies a nodelist, and duplicates the last item in the list:
<xf:insert nodeset="list/y" />
The result would be:
<list>
<y>y1</y>
<y>y2</y>
<y>y3</y>
<y>y3</y>
</list>
It is also possible to indicate a position to copy the last node in the list to. The at attribute indicates which node in the nodeset to place the copied node after:
<xf:insert nodeset="list/y" at="1" />
The result would be:
<list> <y>y1</y> <y>y3</y> <y>y2</y> <y>y3</y> </list>
To place the copied node before the node referred to by @at, use the position attribute with a value of before:
<xf:insert nodeset="list/y" at="1" position="before" />
The result would be:
<list> <y>y3</y> <y>y1</y> <y>y2</y> <y>y3</y> </list>
Note that the default value for @position is after, so the following are equivalent:
<xf:insert nodeset="list/y" at="1" /> <xf:insert nodeset="list/y" at="1" position="after" />
The @at attribute is actually an XPath expression, so it is possible to calculate an insert position at run-time. The expression is evaluated in the context of the first node in the @nodeset, as illustrated by the following example:
<xf:insert nodeset="list/y" at="count(../y) - 1" position="before" />
The result would be:
<list> <y>y1</y> <y>y3</y> <y>y2</y> <y>y3</y> </list>
It is also possible to copy a node from some other location than the last node in the list, by using the origin attribute. The node could be in the target nodelist:
<xf:insert nodeset="list/y" origin="list/y[1]" />
which would give:
<list>
<y>y1</y>
<y>y2</y>
<y>y3</y>
<y>y1</y>
</list>
But the node to copy need not be in the nodeset being updated, and could come from some other location, such as a collection of templates:
<xf:insert nodeset="list/y" origin="templates/x" />
The result would be:
<list>
<y>y1</y>
<y>y2</y>
<y>y3</y>
<x>x1</x>
</list>
If the nodeset referred to is empty then the node being copied is created as a child of the insert handler's context node (if you are not clear on the evaluation context then read this):
<xf:group ref="list">
<xf:repeat nodeset="y">
<xf:output ref="." />
</xf:repeat>
<xf:trigger>
<xf:label>Add new 'x'</xf:label>
<xf:action ev:event="DOMActivate">
<!--
The evaluation context here is still 'list', from
the xf:group, above.
-->
<xf:insert nodeset="y" origin="../templates/x" />
</xf:action>
</xf:trigger>
</xf:group>
In many situations there may be no natural 'context', such as the group in the previous example. In these circumstances we use the context attribute to set a context explicitly:
<xf:insert context="list" nodeset="y" origin="../templates/x" />
Note that the @origin value is also evaluated relative to the evaluation context.
XForms provides the getInstanceDocument method to allow access to instance data within a model. An example of its use is here.
The XForms submission module provides many different ways to read data into a form, and to send processed data back out again.
This introduction contains a high-level look at XForms submission. For those new to a declarative approach to programming it's probably easier to understand what XForms submission does by looking at the kinds of procedural code it replaces. Since the approach most familiar to web programmers will be the use of JavaScript in an Ajax application we'll begin there, before then moving on to XForms.
Ajax emphasises asynchronous programs, by way of JavaScript. Programmers used to languages such as Java or C++ will probably find the idea quaint. But as the explosion of interest in Ajax in the last few years has shown, programmers using HTML forms as their application's front-end were crying out for a way to remove unnecessary round-trips to the server and page refreshes.
There are a number of ways this can be achieved in Ajax programming, but the most common is to avoid using HTML forms, and instead use a separate object which can retrieve data directly. This data can then be used to update small parts of the display, and of course dramatically improves the user experience.
Recall that the only reason we're looking at the way data is transferred using script, is as a way to understand what it is that XForms is making easier. If you're looking at XForms with an Ajax background, then this will be useful information, but if you've not used script or Ajax before, don't feel the need to learn any of the following constructs--just try to get a high-level view.
The most common way of doing an Ajax request is to set up script like this:
var req;
function loadXMLDoc(url) {
// native XMLHttpRequest object
if (window.XMLHttpRequest)
{
req = new XMLHttpRequest();
req.onreadystatechange = readyStateChange;
// IE/Windows ActiveX version
} else if (window.ActiveXObject)
req = new ActiveXObject("Microsoft.XMLHTTP");
if (req);
{
req.onreadystatechange = readyStateChange;
req.open("GET", url, true);
req.send();
}
}
function readyStateChange() {
// '4' means document "loaded"
if (req.readyState == 4)
{
// HTTP 200 means "OK"
if (req.status == 200)
{
// do something here
} else
{
// error processing here
}
}
}
loadXMLDoc("http://example.com/customers/my-customer.xml");
A more complete approach would look to handle the error conditions, allow more than one submission to be running at the same time, and so on. But since that would make the code more complicated, not less, the snippet we have gives us plenty to help us understand what it is that a data submission is generally doing.
Let's map the procedural code that we've just seen to the XForms equivalent:
<xf:submission method="get" action="http://example.com/customers/my-customer.xml">
<xf:action ev:event="xforms-submit-done">
<!-- do something here -->
</xf:action>
<xf:action ev:event="xforms-submit-error">
<!-- error processing here -->
</xf:action>
</xf:submission>
Here we can see that we've specified the same things that we did in the Ajax approach--the URL that we want to get data from, the actions to perform if there is an error, and so on--but the difference here is that this is all we've had to do. Everything else involved in the submission has been done for us.
This idea of simply 'filling in the blanks' will be very familiar to anyone who takes a 'pattern' approach to their development. By re-using commonly occurring programming 'patterns', programmers are able to develop applications more quickly and more accurately. A pattern is not necessarily a library of code though, but is more a set of relationships between high-level, abstract components.
Let's think of our XForms submission processing as a pattern. What we have is some behaviour that is defined in a pretty abstract way, along the following lines:
'get' some data from one URL, or 'put' some data to another URL, and when done, generate either a 'success' event or a 'fail' event.
The strength of any pattern is in the fine balance between being sufficiently general that it can apply to many situations, but not so general that it says nothing. In this case it does seem quite vague; all we seem to have said is 'get a document from here and put it there, and let me know when you've finished'. But when you think about it, that pretty much covers most of the basics for a lot of things we'd like to do: sending an email, saving a file to disk, retrieving an RSS feed, posting a document to a server, and so on. It certainly captures the basic Ajax request we looked at earlier.
By operating at such a high-level of abstraction, we provide a lot of power to the programmer whilst also reducing the amount that they need to learn to perform new tasks.
To illustrate, let's look at the XForms mark-up for retrieving a file from disk:
<xf:submission
method="get" action="file:my-customer.xml"
/>
As you can see, unlike with other programming paradigms, no new API is needed to make the shift from HTTP to the file system. In other words, any author/programmer who mastered the most common technique--getting and putting data to or from an HTTP server--has already got the complete skill-set for getting and putting the same document to or from the hard-drive.
Note also that if the code was written as a relative path:
<xf:submission method="get" action="my-customer.xml" />
then the same XForm would work regardless of whether it was running on a server, or being loaded from a local disk.
Let's go further and see what new skills our author needs to acquire to send the same XML document by email--and whether we can save them the cost of buying a book on MAPI, and the time needed to read it:
<xf:submission
method="put" action="mailto:john.doe@example.com?subject=Results"
/>
As before, no new API to learn. No need to roll your sleeves up and get to grips with MAPI so that you can write a powerful application that will allow a user to enter some XML data, validate it, and then send it via email.
This 'hiding' of functionality is the key point. It's not that we've hidden a whole load of script behind a set of libraries or common functions--which of course it goes without saying that we have--but rather that in XForms we have a simple language that captures an enormous amount of generic functionality. But crucially, that generic language still allows us to use the functionality in very specific situations.
To put it the other way round, we still get specific functionality, such as saving files, sending emails, storing data in a database, and so on, but without having to learn lots of specific APIs. (And we haven't even begun to look at the other features this simple pattern captures, from validation through to different data serialisation options, which make this an even more powerful technique than we've seen so far.)
In the following sections will look at how to use the power of submission in more detail.
The submission element is not an action handler, or a form control, but a collection of attributes that guide: how to obtain some data to be sent, whether to validate it first, how to transport the data, what to do with any data that is returned, and so on.
Submission should therefore be seen as a series of phases, with the various attributes controlling what happens at each step of the process:
xforms-submit event to indicate that sending data is about to begin;xforms-submit-done is dispatched;xforms-submit-error is dispatched.Note that if new data arrives and is placed in a target, a rebuild of the model that holds the instance will be necessary.
We'll look at all of these steps in more detail in the following sections.
The data to be submitted is selected by evaluating the expression in the ref attribute. If no attribute is present, then its value is "/", which in effect means the first instance in the containing model. If a value for @ref is present, it represents the root of a fragment which will be used to find the data to be submitted. This obviously means that it is not necessary to submit an entire instance--smaller parts can be sent.
Sometimes an author will not want to serialise any data for submission, and for this reason XForms 1.1 adds the serialize attribute which can be set to false to indicate no data should be sent. But assuming this attribute is set to true, or is not set at all, then starting at the root of the fragment, all relevant nodes--i.e., all nodes that have their relevant Model Item Property evaluating to true--are collected to make up the data to be sent.
A common pattern is for authors to use relevance only to control the way that the user interface appears to their users (since non-relevant controls are hidden), but to still want all of the data in the instance to be submitted. To make this possible, XForms 1.1 introduced the relevant attribute, which can be set to false to indicate that even non-relevant nodes should be serialised for submission.
Note that the value in @ref also sets the evaluation context for any XPath expressions that might occur within the submission element.
The submission 'method' indicates the operation to be performed. Although this will often be a simple mapping to an HTTP method, it needn't be. For example, the following code sets up a submission declaration that when called will push data up to a server, using an HTTP PUT:
<xf:submission action="http://www.example.org/customer/3.xml" method="put" />
However, the following submission declaration differs only in that the file: protocol is used, yet it has a completely different effect--instead of sending data over the network using the HTTP protocol, the data is simply saved to a local file:
<xf:submission action="file:///my-files/customers/customer/3.xml" method="put" />
In other words the submission method is more than just an HTTP method.
(For more on the file: protocol, see Manipulating local files.)
The best way to understand the submission method therefore is as a request to the XForms processor to do something, but without having to be protocol-specific about what you want doing. This ability to specify what you want to do in a high-level way is very powerful. For example, say we would like to create a form that uses both of the URLs we had above--the customer on the server, and one on our local drive. The easiest way to achieve this would be for our form to use relative paths, and then if we run the form from our disk it saves the information to the local file, and if we run the form from the server it sends the data to the server. In terms of specifying the location it's easy; we just use relative paths as we might do for an image or stylesheet in HTML. But the method would normally be more of a problem, since the action to 'save a file' is completely different to the action to 'send data to a server using HTTP'. However, since the submission method is merely a request to the XForms processor to do something in a protocol-independent way, then the following mark-up will work in both of our scenarios--server and local drive alike:
<xf:submission action="customers/customer/3.xml" method="put" />
In short, the exact meaning of 'put' will be determined by the context in which it is being used, and it might even mean 'send an email':
<xf:submission action="mailto:accounts@example.org" method="put" />
We saw above that the submission method is usually specified using the method attribute; this is true in both XForms 1.0 and XForms 1.1, However, in XForms 1.1 the submission method can also be set using the method child element, which will override the method attribute if both are specified.
(Note that there is no default value for the submission method so either an attribute or element will be needed.)
The element technique allows us to set the submission method at run-time which is a particularly useful way of reusing one submission declaration for a number of different purposes, as we often want to do when building RESTful applications.
For example, in our earlier examples we had some customer information stored at:
customers/customer/3.xml
In a RESTful application we might allow this data to be updated with a 'put' or removed completely with a 'delete'. With XForms 1.0 we'd have to have two separate submission declarations:
<xf:submission id="sub-put" action="customers/customer/3.xml" method="put" /> <xf:submission id="sub-delete" action="customers/customer/3.xml" method="delete" />
However, in XForms 1.1 we can reuse one submission declaration for both tasks, as follows:
<xf:submission id="sub-customer" action="customers/customer/3.xml"> <xf:method value="method" /> </xf:submission>
Now that this submission declaration has been created, it can be used in any number of ways. For example, the submission method need not be set until the user chooses an action:
<xf:trigger>
<xf:label>Save</xf:label>
<xf:action ev:event="DOMActivate">
<xf:setvalue ref="method">put</xf:setvalue>
<xf:send submission="sub-customer" />
</xf:action>
</xf:trigger>
<xf:trigger>
<xf:label>Delete</xf:label>
<xf:action ev:event="DOMActivate">
<xf:setvalue ref="method">delete</xf:setvalue>
<xf:send submission="sub-customer" />
</xf:action>
</xf:trigger>
Alternatively, if we want to keep things more encapsulated, we might put this functionality into the submission declaration itself:
<xf:submission id="sub-customer" action="customers/customer/3.xml">
<xf:method value="method" />
<xf:action ev:event="my-save">
<xf:setvalue ref="method">put</xf:setvalue>
<xf:send submission="sub-customer" />
</xf:action>
<xf:action ev:event="my-delete">
<xf:setvalue ref="method">delete</xf:setvalue>
<xf:send submission="sub-customer" />
</xf:action>
</xf:submission>
This provides us with a convenient way of centralising any interaction with the customer into one place, which is particularly useful for keeping code up-to-date, or for managing processes that need to run when another has completed (or failed). To use this submission declaration the triggers we saw earlier would need to change to:
<xf:trigger>
<xf:label>Save</xf:label>
<xf:action ev:event="DOMActivate">
<xf:dispatch name="my-save" target="sub-customer" />
</xf:action>
</xf:trigger>
<xf:trigger>
<xf:label>Delete</xf:label>
<xf:action ev:event="DOMActivate">
<xf:dispatch name="my-delete" target="sub-customer" />
</xf:action>
</xf:trigger>
This makes the purpose of the code easier to see and read, and consequently easier to maintain.
After the selection step, the data to be submitted is then validated. No validation events are dispatched during this step, but the whole process of validation is the same as defined in xforms-revalidate. If any of the selected data has its required property set, but is found to be empty, or is invalid according to its constraint properties or a schema, then submission processing is stopped after dispatching event xforms-submit-error.
Since there will be situations where invalid data needs to be submitted--for example to save an incomplete form--XForms 1.1 introduced the validate attribute, which can be set to false to prevent this step of the processing. Validation is also disabled if @serialize is set to false, but since there may be situations where in invalid instance should halt submission, even if no data is being serialised, this can be overridden by setting @validate explicitly.
Although submission is a powerful feature of version 1.0 of XForms, there are a number of common use-cases that it is unable to support. For example, although it's easy for an XForms 1.0 form to request an RSS feed and to display the data with a repeat, it's not possible for the user to specify the feed location at run-time, perhaps by typing a URL into an input control, or selecting from a list. XForms 1.1 adds a number of new features to submission to control the URL, request headers, location of returned data, and more, which together make it much easier to build forms that use servers based on REST, ATOM, SOAP, and WebDAV.
In addition to the RSS feed example, other configurations that require URIs to be defined at run-time include REST-based services. For example, Basecamp incorporates the user's name into the URL, and as such it is impossible to create a generic form with XForms 1.0, even though XForms is ideal for communicating with such services.
Perhaps the most important example of a service that can't be used with XForms 1.0 is ATOM. Whilst at a push a form could be created to provide support for Basecamp (by creating one form per person!) there is simply no workaround that would allow ATOM to be used, since the URL that should be used for adding and editing items is provided within the item itself (a process called introspection).
Just as important as setting the URL at run-time is the need to specify request headers; both WebDAV and SOAP, for example, require header values to be set, as does HTTP authentication. (Whilst most XForms processors support HTTP authentication using the usual pop-up login box, by being able to set headers on a request it's possible for a form author to create their own login mechanism.)
A good example of the need for headers is when making a SOAP request. The action to be performed on the server is usually passed as a header in the HTTP request. Using the XForms 1.1 additions, this could be configured as follows:
<xf:submission
ref="instance('inst-request')"
method="post"
replace="none"
>
<xf:resource value="instance('inst-control')/submission/@action" />
<xf:header>
<xf:name>SOAPAction</xf:name>
<xf:value value="concat(
'"http://schemas.xmlsoap.org/wsdl/http/',
local-name(instance('inst-request')/soap:Body/*),
'"'
)"
/>
</xf:header>
</xf:submission>
One nice feature of this example is that the 'method' to use in the SOAPAction header is calculated for us automatically from the actual SOAP payload.
Whatever technique is used to get the URI into the submission, a little trick you can use to allow the user to choose a file is to use the xf:upload control to obtain a URL, as follows.
First, create a submission that uses a dynamic URL:
<xf:submission
ref="instance('inst-request')"
method="post"
replace="none"
>
<xf:resource value="instance('inst-control')/submission/@action" />
</xf:submission>
Next give the node that will hold the URL a datatype of 'xsd:anyURI', so that it can be used with the xf:upload control:
<xf:bind nodeset="instance('inst-control')/submission/@action" type="xsd:anyURI" />
Finally, use an xf:upload control to allow the user to provide a URL:
<xf:upload ref="instance('i-config')/submission/@action">
<xf:label>Choose file</xf:label>
</xf:upload>
The xf:upload control is often used to 'upload' the contents of a file into the instance data, but by binding the control to a node that has a datatype of xsd:anyURI we tell the control that all we want is the URL for the file, and not the file itself.
New features are due to be added to the XForms specification, to allow submission to handle incoming non-XML data. A new possible value for the replace attribute--text--prevents a submit-error occurring on receipt of non-XML data, whilst a new attribute--target--contains an XPath expression indicating the node into which response data is to inserted.
Here is an example of how to use the new attributes to insert the content of the file SomeTextFile.txt into the node /x/y in the instance data.
<xf:model>
<xf:instance id="i0">
<x>
<y />
</x>
</xf:instance>
<xf:submission
method="get" action="SomeTextFile.txt"
replace="text" target="y"
/>
</xf:model>
Although this has not been fully discussed and decided by the PTB, here is my interpretation of the changes to be made to Section 11: Submit of the XForms spec:
5.
Once the XML instance data has been replaced, the rebuild, recalculate, revalidate and refresh operations are performed on the model, without dispatching events to invoke those four operations. Submit processing then concludes after dispatching xforms-submit-done.
More interesting uses can be seen when submitting to a server that supports XPointer queries. Consider the situation where a single value is required from a large XML document; prior to the introduction of this feature, the following unwieldy code would be required:
<xf:model>
<xf:instance id="iRS">
<dummy />
</xf:instance>
<xf:instance id="i">
<x>
<y />
</x>
</xf:instance>
<xf:submission
method="get" action="AnEnormousXMLFile.xml"
instance="iRS" replace="instance"
>
<setvalue ev:event="xforms-submit-done"
ref="instance('i')/y"
value="instance('iRS')/some/deeply/nested/node"
/>
</submission>
</model>
With the target attribute, and @replace="text", it can be condensed into the following:
<xf:model>
<xf:instance>
<x>
<y />
</x>
</xf:instance>
<xf:submission
method="get" action="AnEnormousXMLFile.xml#xpointer(/some/deeply/nested/node/text())"
target="y" replace="text"
/>
</xf:model>
A spin-off from this feature is that the target attribute can also be used in conjunction with @replace="instance", and so build up complex documents by inserting retrieved sub-trees into the current instance, instead of replacing it entirely. This can be particularly handy when dealing with large XForms documents containing instance data with potentially many optional sections. In this way, it can work like relevance, but in reverse, adding the sections you need, rather than disabling those you don't.
For example:
<xf:model>
<xf:instance>
<x>
<y />
</x>
</xf:instance>
<xf:submission
method="get" action="ASmallXMLFile.xml"
target="y" replace="instance"
/>
</xf:model>
The above example would insert the XML content of the document at ASmallXMLFile.xml, into the y element.
XForms processors are not required to support the file: protocol, but both formsPlayer and the Firefox extension do. The methods applicable to file: will usually be get and put. In formsPlayer, the approach is to prompt the user on any load or save of a local file, since this is obviously a very powerful technique.
In addition to the file: protocol, formsPlayer implements an experimental protocol called cookie:, which allows forms to load and save XML documents locally without the user needing to be prompted. This is deemed safe because the location for the file is only ever going to be in a sub-directory in the user's local store (i.e., home directory). There is therefore no way for a form to get out of this area and load or overwrite system files.
In addition, the URL for the sub-directory is based on the domain of the site being used, so there is also no way for a form from one site to get access to data from other sites (sites can share data across forms, though, which is very useful).
Typical uses are chaining forms together, where the output for one form is the input to the next, or saving state across an application. An example of the latter is illustrated in our Google Desktop app (to be added) which allows you to save searches across sessions.
Whether using file: or cookie:, a common requirement is the need to create the initial file on first use of a form. This is a little tricky, because if you initialise your instance using @src you have no way to detect that the file doesn't exist. The following technique is used by our RSS Reader side-bar (part of the formsPlayer install, and discussed at [to be added]):
@src. In the rest of this example, the instance is referred to as i-list.cookie: protocol, but it could be loaded via the file: protocol:
<xf:submission
id="sub-open-list"
method="get"
ref="instance('i-list')"
action="cookie://feed-list.xml"
replace="instance"
>
<!--
If we fail to load the list, then this is probably
the first time we've been run, so save the default
list.
-->
<xf:action ev:event="xforms-submit-error">
<xf:message level="modal">
RSS Reader has failed to open your local data file.
This may be because this is the first time you have
used the RSS Reader. A default data file will be
created which includes a small number of feeds.
</xf:message>
<xf:send submission="sub-save-list" />
</xf:action>
</xf:submission>
Note the behaviour in the error handler (when the file doesn't exist)--we simply save our initial instance data, which has been placed inline in the instance i-list).
xforms-ready handler that triggers this get, i.e., which loads your local document:<xf:send submission="sub-open-list" ev:event="xforms-ready" />
(Note that you have now effectively emulated the behaviour of @src, but with all the benefits of submission, such as having notification events as well as the ability to use instance data.)
In normal operation, using sub-open-list will load the previously saved file, overwritting the inline instance data. But on first use there will be no file, and so the error handler will be run, and cause the inline instance data to be saved.
submission to save the list to the file (you don't need to set a 'dirty flag' but it is quite useful):
<xf:submission
id="sub-save-list"
method="put"
ref="instance('i-list')"
action="cookie://feed-list.xml"
replace="none"
encoding="ISO-8859-1"
omit-xml-declaration="false"
indent="true"
>
<xf:setvalue bind="dirty-flag" ev:event="xforms-submit-done">
false
</xf:setvalue>
</xf:submission>
Note that you would need these two submissions anyway, to load and save the file; the only additional bit we're adding here is the use of xforms-ready to invoke the submission that does the loading, and the use of xforms-submit-error on that load to invoke a save of our initial data.
This tutorial is for people who are familiar with the basics of XForms, have knowledge of some server-side processing langauge, and want to combine the two. In this section we'll look at how to do this with PHP, ASP, Java Servlets and ColdFusion.
To run the examples in this document you will need to have installed formsPlayer. For the ColdFusion example you should have installed and configured ColdFusion 6.1. The ASP example requires IIS and the Java Servlet example requires Jakarta Tomcat or an equivalent. The PHP example was tested with PHP version 4.3.7 and IIS.
The source for the form and the server-side scripts is available from here.
The examples that are listed in this document all use the same simple XForm. It contains only one XForms model and one set of instance data. The XForm holds a few pieces of information about a person such as their name, age, gender, address, etc. The full instance data is as shown below:
<person xmlns="">
<firstname>Joe</firstname>
<surame>Bloggs</surame>
<gender>M</gender>
<age>32</age>
<phone>02085555555</phone>
<email>joe.bloggs@example.com</email>
<address>
<addr1>1a Street</addr1>
<addr2>A Town</addr2>
<addr3>A City</addr3>
<addr4>Post Code</addr4>
</address>
</person>
Each piece of instance data is referenced by a single XForms control. The XForms data is submitted to a resource by using the submission element. For the ASP example the submission element will look something like this (depending on the structure of your IIS site):
<xf:submission id="sub-save-person" method="post" action="http://localhost/EchoPerson.asp" />
NOTE: Although the same XForm is used in each example in the following sections, you will need to change the submission elements' action attribute for each one so that the form data is submitted to the correct resource.
This example shows how to obtain and manipulate submitted XForms instance data within an Active Server Page. Our ASP is called EchoPerson.asp, which takes the submitted XML and outputs it to the browser in a tabular format. (The full ASP code is available here.)
In the ASP, to get hold of the XML instance data, we create an MSXML DOMDocument and load the HTTP Request (the submitted XML) into it, using the load method, as follows:
...
var oXML = new ActiveXObject("MSXML2.DOMDocument");
oXML.load(Request);
...
Now we have the instance data in the DOMDocument we can retrieve values from it to set our script variables. Using the selectSingleNode XPath function we return a node from which we can get the value:
...
var oFirstName = oXML.selectSingleNode("/person/firstname");
var sFirstName = oFirstName.text;
var oSurname = oXML.selectSingleNode("/person/surname");
var sSurname = oSurname.text;
...
Now we have the instance data values stored into our JavaScript variables we can do as we want with them, in this case just format them for display using an HTML table:
...
var sHTML = "| First name : | " + sFirstName + " |
| Surname : | " + sSurname + |
Once we have built up the complete HTML string we return it to the browser:
...
Response.Write(sHTML);
To try this for yourself:
1. Extract the person.html and EchoPersonDataASP.asp files from the xfserverscripts.zip (see 'Prerequisites') to the root directory of your default IIS site.
2. Open the person.html file in notepad, or other text editor, and change the submission element action attribute to "EchoPersonDataASP.asp".
3. Open up Internet Explorer and go to http://localhost/person.html
4. Change any data and submit the form.
As with the previous examples, this one shows how to output the submitted XForms data in an HTML table but this time we are using ColdFusion 6.1 as the scripting language. (The full ColdFusion code is available here.)
After submission of the XForm, the first thing to do in the ColdFusion page is to get hold of the request data and put it into a variable. This is achieved using the GetHttpRequestData() system function:
...
...
The GetHttpRequestData() function gets ALL of the request data, the headers and the body. We are only interested in the body of the request in this example which is obtained with the content parameter. Once we have that, we convert it to a string using ToString and parse it, using the XMLParse function:
...
...
Now we have the XML document available to our ColdFusion page we can do what we want with the data. In our sample it is just to display the values in an HTML table:
...
<td>
First name
</td>
<td>
#xmlDoc.person.firstname.XmlText#
</td>
...
To try this example for yourself:
1. Install and configure ColdFusion 6.1 (Trial version available from Macromedia site).
2. Extract the person.html and showdata.cfm files from the xfserverscripts.zip (see 'Prerequisites') to the same directory in the site you have configured to use with ColdFusion.
3. Open the person.html file in notepad, or other text editor, and change the submission element action attribute to "showdata.cfm".
4. Open up Internet Explorer and go to your website eg. http://localhost/person.html
5. Change any data and submit the form.
This example shows one way to obtain and manipulate submitted XForms instance data within a Java Servlet. Our Servlet is called EchoPersonDataServlet, which takes the submitted XML and outputs to the browser in a tabular format the names and values of the XML elements. Any empty elments are ignored. (The full PHP code is available here.)
NOTE: If you wish to compile and use the EchoPerson Servlet with Tomcat you will need to have added the following .jar files to your classpath. %TOMCAT_HOME%\common\lib\servlet-api.jar, %TOMCAT_HOME%\common\endorsed\xercesImpl.jar and %TOMCAT_HOME%\common\endorsed\xmlParserAPIs.jar. You will also have to configure the web.xml file in your web application to reference the Servlet.
In the Servlet, to get hold of the XML instance data, we read in the body of the request to a string:
...
String xml = new String("");
String thisLine = new String("");
...
BufferedReader br = request.getReader();
while ((thisLine = br.readLine()) != null) {
xml = xml.concat(thisLine);
}
...
Now we have the instance data in a string we will use DocumentBuilderFactory and DocumentBuilder objects along with the Document interface to help manipulate it. From java.sun.com - "The Document interface represents the entire HTML or XML document. Conceptually, it is the root of the document tree, and provides the primary access to the document's data".
...
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(xml)));
...
All the rest of the Servlet does is to iterate through the nodes of the XML and format the names and values of the elements in an HTML table which is returned to the browser to be displayed.
NOTE: How to manipulate the submitted XForms data and whether to use DOM or SAX to do it is really up to the developer and it depends what the exact needs are. For more information on using XML and Java please take a look at the Java Web Services Tutorial.
To try this example for yourself:
NOTE: Installing and configuring Tomcat goes beyond the scope of this tutorial but listed below are the major steps involved in running this tutorial. For complete instructions on using web applications with Tomcat please follow the documentation available on the Apache website.
1. Install and configure Jakarta Tomcat 5 (download from The Apache Site).
2. Make sure that your CLASSPATH contains references to the servlet-api.jar, xercesImpl.jar and xmlParserAPIs.jar files that are mentioned earlier in this section.
3. Create your own 'webapp' structure and extract the person.html file to the root of it.
4. Extract the EchoPersonDataServlet.java file to the 'classes' directory of your webapp and compile.
5. Use the web.xml file for your webapp to map the EchoPersonDataServlet class to a URL.
6. Open the person.html file in notepad, or other text editor, and change the submission element action attribute to the value of the URL you specified in the web.xml file. Remember Tomcat defaults to use port 8080.
7. Start Tomcat, open Internet Explorer and point it to the location of the person.html file.
8. Change any data and submit the form.
NOTE: You can download the PHP installer from http://www.php.net. This example was tested with the default installation. The PHP 4.3.7 installation also includes expat, an XML toolkit written by James Clark that parses XML in C. This is an event based XML parser which we will make use of.
This example shows how to obtain and manipulate submitted XForms instance data within a PHP script. Our PHP script is called EchoPerson.php, and it simply outputs the received data to the browser in a tabular format. (The full PHP code is available here.)
The first thing we need to do in the script is to make sure that there is some data for us to process. Any submitted data will be available to us in the HTTP_RAW_POST_DATA global variable. We check to see if this has a value and if it has, assign the data to a local variable:
...
if (!empty($GLOBALS['HTTP_RAW_POST_DATA'])) {
$req = $HTTP_RAW_POST_DATA;
...
}
...
Once we have some data, we create an XML parser, set up the event handler functions--using xml_set_element_handler() and xml_set_character_data_handler(), and then set any other options we need with xml_parser_set_option:
...
$xmlParser = xml_parser_create();
xml_set_element_handler($xmlParser, "startElement", "endElement");
xml_set_character_data_handler($xmlParser, "characterData");
xml_parser_set_option($xmlParser, XML_OPTION_CASE_FOLDING, FALSE);
...
The startElement() and endElement() functions are defined further up in the script and these functions are where we put any code to call when the parser comes across a new element or finishes reading an element. The characterData() function is also defined further up the script, and this deals with what to do with data contained inside the element. Within these functions we also reference two global variables $currentEle and $sHTML. The first is to keep track of the name of the current element that is being processed, and the other will hold the HTML that will eventually be displayed:
...
$currentEle = "";
$sHTML = "";
function startElement($parser, $name, $attrs) {
global $currentEle;
global $sHTML;
$currentEle = $name;
if (!ignoreElement($currentEle)) {
$sHTML .= "$name";
}
}
function endElement($parser, $name) {
global $currentEle;
$currentEle = "";
}
function characterData($parser, $data) {
global $currentEle;
global $sHTML;
if (!ignoreElement($currentEle)) {
$sHTML .= "$data";
}
}
...
There is also one more function defined in the script (ignoreElement()) that is used by a couple of the parser event handlers. As we have elements that don't contain data (person and address) we don't want to display them in our HTML table; the elements we don't want to display have their names contained in an array so that we can compare the current element name (global variable $currentEle) with the values in our array, and decide to ignore the element if we have a match:
...
function ignoreElement($eleName) {
$ignore = false;
$ignoreThese = array(0 => "person",
1 => "address",
2 => ""
);
foreach ($ignoreThese as $value) {
if ($value == $eleName) {
$ignore = true;
break;
}
}
return $ignore;
}
...
Now we actually parse the XML using the conveniently named xml_parse() function. This returns either true or false depending on whether there were any errors or not. If there are any errors we just add them to the HTML string that has been built up:
...
if(!xml_parse($xmlParser, $req, TRUE)) {
$sHTML .= "";
$sHTML .= "An error occurred : ";
$sHTML .= xml_error_string(xml_get_error_code($xmlParser));
$sHTML .= "Line : " . xml_get_current_line_number($xmlParser);
$sHTML .= "Column : " . xml_get_current_column_number($xmlParser);
$sHTML .= "";
}
...
After finishing off the HTML table and freeing up the parser memory (using xml_parser_free()) we output the HTML:
...
print $sHTML;
...
NOTE: Documentation for all PHP functions, including those for the XML parser are available online at http://www.php.net/docs.php.
To try this for yourself:
person.html and EchoPerson.php files, and place them in the root directory of your website.person.html in notepad, or some other text editor, and change the submission element action attribute to EchoPerson.php.http://localhost/person.html, or wherever you have located the file.XForms is built on a number of different standards, and perhaps the most important is XPath. This standard is used to indicate which pieces of data should be manipulated by the form, and because the language is extremely powerful, expressions can get quite complex. For this reason XForms also supports a number of ways to abbreviate these expressions, and it's the shorthand techniques what have been used in the examples so far.
XForms uses XPath expressions in many places, and it is important as an author to understand how these expressions are evaluated. Perhaps the most important concept is that of the evaluation context.
When an XPath expression is evaluated, the processor needs to know both the model and the instance against which to evaluate the expression. Since all expressions are evaluated in some context then it should always be possible to work out the meaning of even short expressions such as "/".
To illustrate the context rules, we'll begin with the simplest situation, when the form being processed has only one model and one instance:
<xf:model id="mdl">
<xf:instance id="inst">
<a xmlns="">
<b />
</a>
</xf:instance>
</xf:model>
We'll look first at all the ways we could bind to element b. The first approach is to make everything--both the model and the instance--explicit:
<xf:input model="mdl" ref="instance('inst')/b">
<xf:label>B:</xf:label>
</xf:input>
In a small form we would be unlikely to use this explicit syntax since there are so many shortcuts available to us. But this syntax does have the advantage that it will always refer to element b, regardless of where in our form it is placed. For example, if we wanted to show the value of b in the hint of some other control, we would need to use this explicit syntax if the other control was in another model:
<xf:input model="mdl2" ref="instance('inst2')/d">
<xf:label>D:</xf:label>
<xf:hint>
Please enter a
'<xf:output model="mdl" ref="instance('inst')/b" />'
</xf:hint>
</xf:input>
Perhaps the simplest abbreviation we can make on our earlier mark-up is to make use of the 'First Instance' rule, which says that if no instance is specified, the first instance in the specified model should be used. Our previous example can be altered to remove the use of instance():
<xf:input model="mdl" ref="b">
<xf:label>B:</xf:label>
</xf:input>
We can safely rely on this rule in our example so far, since we only have one instance, but once our forms get to a reasonable size it is probably worth specifying the instance explicitly. That way, if form controls are moved around or a new instance is added to a model, the form will continue to work correctly.
To illustrate the kinds of problems that can arise, in the following example a new instance has been added, but since it has been placed first in the model, the previous reference to b is no longer bound correctly since it relied on the 'first instance' rule, and 'inst3' is now the first instance:
<xf:model id="mdl">
<xf:instance id="inst3">
<c xmlns="">
<d />
</c>
</xf:instance>
<xf:instance id="inst">
<a xmlns="">
<b />
</a>
</xf:instance>
</xf:model>
<xf:input model="mdl" ref="b">
<xf:label>B:</xf:label>
</xf:input>
We can fix this by referring to the instance explicitly, and if we had made our initial references explicit then we would not have had a problem in the first place.
In the same way that if there is no instance specified the first one is implied, so too if no model is specified then the first model will be used. With this in mind, we can further abbreviate our expression:
<xf:model id="mdl">
<xf:instance id="inst">
<a xmlns="">
<b />
</a>
</xf:instance>
</xf:model>
<xf:input ref="/a/b">
<xf:label>B:</xf:label>
</xf:input>
or:
<xf:input ref="b">
<xf:label>B:</xf:label>
</xf:input>
This last control only works because the evaluation context is actually the root element of the instance document (a) and not the document itself. In other words, the expression "*" (which means return all children of the context node) would return b and not a.
When overriding the 'first model' or 'first instance' rules, as discussed above, it is not always necessary to place the model or instance that you want directly onto a control; if there is more than one control then it is certainly more efficient to use a group:
<xf:model id="mdl">
<xf:instance id="inst3">
<c xmlns="">
<d />
<e />
</c>
</xf:instance>
<xf:instance id="inst">
<a xmlns="">
<b />
</a>
</xf:instance>
</xf:model>
<xf:model id="mdl2">
<xf:instance id="inst2">
<c xmlns="">
<d />
</c>
</xf:instance>
</xf:model>
<xf:group model="mdl">
<xf:input ref="d">
<xf:label>D:</xf:label>
</xf:input>
<xf:input ref="e">
<xf:label>E:</xf:label>
</xf:input>
</xf:group>
Rather than having @model on each of the two controls, things are more succinct by setting a new evaluation context for all the controls via a group.
A number of strategies are available in XForms to abbreviate an XPath expression. Which one we use for a particular form will depend on how our form will evolve.
This section describes the functions that can be used in XPath expressions.
The XForms Core Function Library includes the entire XPath Core Function Library, including operations on node-sets, strings, numbers, and booleans. In addition XForms adds the functions described in the following sections.
number days-from-date(string)
This function returns a whole number of days, according to the following rules:
xsd:date or xsd:dateTime, the return value is equal to the number of days difference between the specified date or dateTime (normalized to UTC) and 1970-01-01.Examples:
days-from-date("2002-01-01")
returns 11688
days-from-date("1969-12-31")
returns -1
string days-to-date(number)
This function returns a string containing a lexical xsd:date that corresponds to the number of days passed as the parameter according to the following rules:
1970-01-01.NaN results in output of the empty string.Examples:
days-to-date(11688)
returns 2002-01-01
days-to-date(-1)
returns 1969-12-31
node-set instance(string)
An XForms Model can contain more that one instance. This function allows access to instance data, within the same XForms Model, but outside the instance data containing the context node.
The argument is converted to a string as if by a call to the string function. This string is treated as an IDREF, which is matched against instance elements in the containing document. If a match is located, and the matching instance data is associated with the same XForms Model as the current context node, this function returns a node-set containing just the root element node (also called the document element node) of the referenced instance data. In all other cases, an empty node-set is returned.
Example:
For instance data corresponding to this XML:
<xforms:instance xmlns="" id="orderform">
<orderForm>
<shipTo>
<firstName>John</firstName>
</shipTo>
</orderForm>
</xforms:instance>
The following expression selects the firstName node. Note that the instance function returns an element node, effectively replacing the leftmost location step from the path:
ref="instance('orderform')/shipTo/firstName"
number power(number, number)
Raises the first argument to the power of the second argument, returning the result. If the calculation does not result in a real number, then NaN is returned.
Examples:
power(-1, 0.5)
returns NaN.
if (prin>0 and dur>0 and rate>0, prin*rate/(1-power(1+rate, -dur)), 0)
returns a compounded interest payment value given a non-zero principal (prin), duration (dur) and periodic interest rate (rate).
formsPlayer adds some additional functions.
The standard XForms function instance() must only return instances from the model of the current evaluation context, and although trying to obtain an instance from another model won't result in an error, the return value will be null.
The rationale is to improve performance of XForms processors, since if an author is unable to create dependencies between models, then calculations for each model can be performed on each model individually as necessary, rather than for every model in a form.
Although authors are prevented from creating depencies between models, there will still be occassions when data needs to be moved from one model to another. For this reason formsPlayer adds the globalInstance() function, which can return a reference to any instance in the form. Note however, that if this function is used in bind statement no dependency is created; in other words, if the data in an instance in another model changes, the bind statement will not automatically be recalculated. If a recalculation is needed, then it is the author's responsibility.
With formsPlayer it is possible to write your own XPath functions to manipulate instance data within the XForms data processing model. One example of doing this is discussed in the context of controlling a 3D viewer, which shows how to create inline functions (i.e., functions that are defined in the current source document).
It's also possible to create XPath functions which are methods on COM components.
The latest Working Draft of XForms 1.1 provides support for a number of new attributes on action to allow for conditional execution and looping. These attributes have been implemented in formsPlayer since version 1.4.3, and this section explains how to use them.
The if attribute indicates that an action block should only be executed if the XPath expression used evaluates to true().
For example, if our application was looping through a list of RSS feeds to process, once it reaches the end of the list we would set the feed counter back to 1 and save any new items we had read.
The following mark-up tests for a loop counter to be greater than the count of the number of RSS feeds to process, and when it is, it resets the counter and invokes a submission:
<xf:action if="@i > count(feed)"> <xf:setvalue ref="@i" value="1" /> <xf:send submission="sub-put-unread-items" /> </xf:action>
For more information and further examples see Section 3.4 of XForms 1.1.
The iterate attribute allows our applications to repeatedly perform the same action across a nodeset.
For example, an application that has retrieved data from an RSS feed may need to convert any dates from RFC 288 format to ISO 8601 format (the latter is used by XML Schema and therefore XForms). Also, since pubDate is optional in RSS feeds, the appication may need to create a default date if none is provided. It could do this by looping through all of the returned items and then either adding or converting pubDate.
The first step is to create a handler that will execute when the RSS feed has been loaded successfully:
<xf:action ev:observer="sub-get-rss-feed" ev:event="xforms-submit-done">
Next we set up an iterator across each of the items in the loaded RSS channel:
<xf:action iterate="instance('inst-current-feed-rs')/channel/item">
The action handler then checks that there is actually a pubDate element, and if there is, it is converted to ISO 8601 format:
<xf:action if="*[local-name()='pubDate']">
<xf:setvalue ref="*[local-name()='pubDate']"
value="inline:convrfc288datetoiso8601(.)" />
</xf:action>
Note that the evaluation context within this action handler will be each of the nodes of the nodeset, in turn. However, since @if itself does not change the evaluation context, the setvalue action is actually being evaluated in the context of the action with the iterate attribute, and not the action with the if attribute.
There is no way to add an 'else' branch, so if we want to perform some action if there is no pubDate element, we have to repeat our previous test and use not(). If no pubDate element is present then the following code simply creates a new pubDate element and then sets it to a default value of the current date and time:
<xf:action if="not(*[local-name()='pubDate'])">
<xf:duplicate origin="instance('inst-control')/templates/pubDate"
ref="." />
<xf:setvalue ref="*[local-name()='pubDate']" value="now()" />
</xf:action>
</xf:action>
</xf:action>
For more information and further examples see Section 3.5 of XForms 1.1.
Since formsPlayer is hosted inside Internet Explorer, it relies on IE for its CSS support, which is mainly CSS 1, plus a little of CSS 2. This means that fP does not support namespaces, or the new pseudo-elements and pseudo-classes defined for XForms. However, there are various workarounds that have been added to give the author control over the look-and-feel of their forms, and many of these workarounds have been adopted by other processors.
Assuming that the XForms namespace has been defined with the prefix xf, setting all XForms input controls to have a blue background would require the following style rule:
xf\:input
{
background-color: blue;
}
Note that unlike the 'proper' namespace support defined in CSS 2.1, the prefix used here must exactly match the prefix used in the mark-up since all that is happening is the element called 'xf:input' being matched.
Pseudo-classes are classes that are automatically added and removed from controls in a form. For example, if a control is bound to some data that is invalid then the control will have the pseudo-class :invalid, but the moment that the data becomes valid again, the control will have the pseudo-class :valid. This change of state is carried out by the processor, and the author does not need to write any code to take advantage of this.
The :blurred pseudo-class is applied to controls that have received focus at some point. A typical use is to style invalid controls in such a way that they are not shown as invalid (for example, with a red border) until the user has visited them.
A control that has had the focus can be styled as follows:
.pc-blurred
{
border : 1px solid blue;
}
The :enabled and :disabled pseudo-classes are applied to controls when the instance data they are bound to becomes relevant or not.
The CSS rules to use for these pseudo-classes are:
.pc-enabled {
display : block;
background-color : gray;
}
and
.pc-disabled {
display : none;
}
The :focus pseudo-class is applied to controls when they receive focus. A use could be to draw attention to the current field when a user tabs through controls on a form.
The CSS rule to style a control that has focus is:
.pc-focus {
border : 1px solid green;
}
The ::hover pseudo-class is set on an element when the mouse is over it.
The following examples changes the background colour of items in a repeat when the mouse is over them:
xf\:repeat .pc-hover
{
background-color : silver;
}
The :read-only and :read-write pseudo-classes are applied to controls when the instance data they are bound to has a model item property of readonly with a value of true or false.
The CSS classes needed to style controls are:
.pc-read-only {
background-color : #E0E0E0;
}
.pc-read-write {
background-color : #FFFFFF;
}
The :required and :optional pseudo-classes are applied to controls when the instance data they are bound to becomes required or not via the model item property 'required'.
The CSS rules to use for these pseudo-classes are:
.pc-required {
border : 1px solid red;
}
and
.pc-optional {
border : 0px;
}
The :valid and :invalid pseudo-classes are applied to controls when they are made valid or invalid by instance data model item properties such as 'type'.
The CSS rules to style controls that valid or invalid are:
.pc-valid {
}
and
.pc-invalid {
border : 1px solid red;
}
The pseudo-element ::value is represented as an element with a class of 'value'. To style this element we would normally use the following syntax:
xf\:input .value
{
width: 100px;
}
Note the space between xf\:input and .value; this is the CSS syntax for 'descendant of'. However, in the current formsPlayer architecture this won't work, and there are two alternatives.
The first is that an additional class 'input-value' is available. For example, we can set the size of the pseudo-element ::value on all inputs (i.e., the pseudo-element that would normally be addressed as xf:input::value), by doing this:
.input-value
{
width: 100px;
}
The same pattern is used for all the controls, for example:
.select1-value
{
width: 100px;
}
If you need to set styling on a narrower range of controls then it is possible to use classes:
.fc1 .value
{
width: 100px;
}
<xf:input ref="firstName" class="fc1">
<xf:label>First Name:</xf:label>
</xf:input>
However, the following will not work:
xf\:input.fc1 .value
{
width: 100px;
}
Internet Explorer doesn't provide a way to allow default style rules. This can be confusing for new authors, since all alert messages and non-relevant controls will be viewable by default.
A typical default rule for alert would say that any xf:alert that is a child of a valid form control should be 'hidden':
.valid xf\:alert
{
display: none;
}
A typical default rule for 'disabled' form controls would say that any control that is disabled should be 'hidden':
.disabled
{
display: none;
}
Note that we use display: none; in both cases, since visibility: hidden; reserves the space that an element would take up.
Each of the items in a xf:select1 or xf:select is actually a CSS 'list item'. This means that each item in the selection list can have its appearance controlled using the list-style-image CSS property. The default style is for items in a xf:select1 to have a radio button, and for xf:select items to use a check-box. To indicate that there should be no images, simply set the list style image to nothing:
<style>
xf\:select1.my-select xf\:item xf\:label
{
list-style-image: none;
}
</style>
.
.
.
<xf:select ref="x" class="my-select">
...
</xf:select>
When an item is selected by a user, it acquires the pseudo-class 'selected', and similarly those which are not currently selected have the pseudo-class 'deselected'. With these pseudo-classes it is possible to use any CSS properties to indicate the state of the items to the user. For example, to modify our selection list such that selected items show as white on black, whilst unselected items are to be black on white, we would use the following CSS rules:
.my-select .pc-deselected
{
background-color: white;
color: black;
}
.my-select .pc-selected
{
background-color: black;
color: white;
}
NOTE: In versions prior to 1.5.5.1014 the pseudo-classes are 'selected' and 'deselected'.
When the mouse is moved over an item, that item acquires the pseudo-class 'hover'. The default style for a hovered item is to change the background colour to cyan, but this can be overridden. For example, to set the hover style of selected and unselected items in the xf:select above, we would do this:
.my-select xf\:item.pc-selected.pc-hover,
.my-select xf\:item.pc-deselected.pc-hover
{
background-color: blue;
}
NOTE: In versions prior to 1.5.5.1014 the pseudo-class is 'hover'.
Note that simply using pc-hover wouldn't be sufficient to override this, since the rule needs to be more specific.
The 'selected' and 'deselected' pseudo-classes also allow more specific images to be used to give the form user better feedback. For example, if we have a list of items and want to show a happy face when one is selected, but a sad one otherwise, we would use the following style rules:
.my-select xf\:item.pc-selected xf\:label
{
list-style-image: url("happy.gif");
}
.my-select xf\:item.pc-deselected xf\:label
{
list-style-image: url("sad.gif");
}
The appearance state of a xf:select1 or xf:select is also available as a CSS pseudo-class. To illustrate its use, the following CSS rules will remove the radio buttons for all minimal xf:select1 elements:
xf\:select1.attr-appearance-minimal xf\:item xf\:label
{
list-style-image: none;
}
NOTE: In versions prior to 1.5.5.1014 the class names are 'minimal', 'compact' and 'full'.
By default, xf:select1 is displayed as if appearance were set to 'minimal', whilst xf:select is displayed as if appearance were set to 'full'.
xf:choices can be styled to any level in both minimal and full controls; the default behaviour is to indent each set of choices by 2em per level.
More examples are available in the Subversion repository:
http://svn.x-port.net/svn/public/samples/styling/select/
From version 1.4.3.1030 on, message elements now contain some pseudoelements to make more fine-grained styling available.
The pseudoelements available are:
pe-message-header, pe-modal-header, pe-modeless-header
which correspond to the titlebar of the message, for all messages, modal messages, and modeless messages, respectively.
pe-header-text, pe-modeless-header-text, pe-modal-header-text
which correspond to the text in the titlebar, for all messages, modal messages, and modeless messages, respectively..
The following rules are included in the default css:
.pe-message-header
{
width:100%;
background-color:blue;
padding:0px;
margin:0px;
position:absolute;
top:0;
}
.pe-header-text
{
color:white;
}
The modal/modeless distiguished classes can be accessed in the same way - thus:
.pe-modeless-header-text,
.pe-modal-header-text,
.pe-modal-header,
.pe-modeless-header
{
}
A common requirement is to lay out controls so that labels and data entry areas line up neatly. Most authors assume that tables are needed to do this, but to keep with the spirit of both HTML and CSS, tables should not be used.
The easiest way to obtain a neat layout is to set the size of labels on a control. For example, if all labels were set to 12em then the data entry sections would line up:
xf\:input xf\:label
{
vertical-align : middle;
margin-right : 0.2em;
width : 12em;
}
The dimensions of the entire control can also be set. In this example we set all input controls to take the full width of their container, and to have a dotted line at the bottom:
xf\:input,
{
width : 100%;
padding-top : 0.4em;
padding-bottom : 0.1em;
border-bottom : #d0c49d 1px dashed;
}
We can also set the data entry part of input controls using the technique for setting ::value. For example, to apply a light green background we would do this:
.input-value,
{
background-color : lightgreen;
}
If all of these rules are combined (styling the full input control, the label and the ::value pseudo-element) we would get a layout like this:
If you're not clear on why this happens, you might want to revisit Anatomy of a control.
A key component of an XForms processor is the dependency-engine. The idea is pretty straightforward, and will be familiar to anyone who has used a spreadsheet; if some item has its value set by a calculated expression that contains references to other items, then when any of those items change, the first item must be recalculated. It's not necessary to understand the dependency-engine when programming XForms, but having some familiarity with how it works may help when structuring forms.
To illustrate its use, let's take an instance that has two values which are summed to produce a third, and if the sum is greater than 10, the value is said to be invalid:
<xf:instance>
<instanceData xmlns="">
<a>4</a>
<b>5</b>
<c />
</instanceData>
</xf:instance>
<xf:bind nodeset="c" calculate="../a + ../b" constraint=". <= 10" />
Our calculate instruction says simply that the value of c is the result of summing a and b, which means that every time either a or b changes we want the calculation to be carried out again. Similarly, since the constraint is based on the value of c itself, then if it changes we need to perform the validity check again. This set of dependencies--the first saying that the value of c is dependent on the values of a and b, and the second saying that the validity of c is dependent on the value of c--is easy to create by parsing the XPath expressions in the bind statements. And once created, processing can be quite fast, since it is now possible to only perform calculations that are required by changes in the data.
By working out the dependencies between data items, it is possible to reduce the number of calculations to only those that are implied by some data change. However, if the structure of the data changes, then the whole set of dependencies needs to be recalculated. This process is called a rebuild and involves reevaluating all XPath expressions used in a particular model.
To see why this is necessary, imagine a slightly more complex model than the one we had above, where we have rows in an invoice, and each row has a 'total price' that is calculated by multiplying the number of items purchased by the individual item's selling price:
<xf:instance>
<invoice xmlns="">
<item sku="1223">
<units>2</units>
<price>6.99</price>
<total />
</item>
<item sku="776">
<units>5</units>
<price>12.99</price>
<total />
</item>
</invoice>
</xf:instance>
<xf:bind nodeset="item/total" calculate="../units * ../price" />
Since an XPath expression like "x/y" will select any y element that is a child of an x element, then this simple bind statement causes the XForms processor to create dependencies across all of the total elements; it's as if we had authored the following statements by hand:
<xf:bind nodeset="item[1]/total[1]" calculate="../units * ../price" /> <xf:bind nodeset="item[2]/total[1]" calculate="../units * ../price" />
But there is a big advantage with using the "item/total" technique, which is that if the data is updated in such a way that the number of rows changes, then the processor will automatically create a new set of dependencies. For example, if the user clicks some button to create a new item in the invoice, a new calculation will be automatically added for the new total node, and the effect would be the same as if we had explicitly written the following:
<xf:bind nodeset="item[1]/total[1]" calculate="../units * ../price" />
<xf:bind nodeset="item[2]/total[1]" calculate="../units * ../price" />
<xf:bind nodeset="item[3]/total[1]" calculate="../units * ../price" />
The process responsible for adding this calculation is rebuild.
It is only necessary to reconstuct the dependencies when the structure of the instance data in a model changes. This would be when new nodes are added (using insert) or deleted (using delete), or when some instance data is entirely replaced (using submission with @replace="instance" or reset). In all of these situations the rebuild is automatic.
Although rebuild happens automatically, once you know what will trigger it, you can look at ways of reducing its impact on performance.
Since the process of rebuilding applies to an entire model, it is a good idea to avoid putting unrelated instances into the same model, unless there are no bind statements (and so nothing for rebuild to do). For example, if in our invoice we had an additional instance that is used to retrieve and update customer details, it would be a good idea to place this in a separate model. Otherwise, every time the user requests customer information from the server, the dependency engine would parse all of the XPath expressions for the invoice, updating the dependencies.
Let's return to the simpler instance that we saw before:
<xf:instance>
<instanceData xmlns="">
<a>10</a>
<b>10</b>
<c />
<d />
</instanceData>
</xf:instance>
<xf:bind nodeset="c" calculate="../a * ../b" constraint=". <= 100" />
<xf:bind nodeset="d" calculate="../a + ../b" constraint=". <= 20" />
This gives us four calculations in our dependency graph:
| Target | Type | Expression |
|---|---|---|
c[1] |
calculate | a[1] * b[1] |
c[1] |
constraint | c[1] <= 100 |
d[1] |
calculate | a[1] + b[1] |
d[1] |
constraint | d[1] <= 20 |
as well as the following dependencies:
| Node | Dependents |
|---|---|
a[1] |
c[1], d[1] |
b[1] |
c[1], d[1] |
c[1] |
c[1]'s constraint |
d[1] |
d[1]'s constraint |
We've used the '[]' syntax to convey the idea that if our instance data had more nodes--just as we saw in the invoice example, previously--then these XPath expressions would yield more dependencies.
The meaning of the table is that if any node in the left column changes, the nodes in the dependency list on the right will need recalculating. So if node a changes, then both c and d need to be recalculated; c would be set to a * b and d would be set to a + b. But note further down the table that when c changes, its constraint needs to be recalculated, and similarly, a change in d requires a recalculation of its constraint. A structure like this, where items are linked together, is often called a directed graph.
We've said that you don't need to understand the dependency-engine in order to use it, so you certainly don't need to understand this next step; but if you are inclined towards the nuts and bolts, you'll no doubt find this feature of the XForms architecture interesting.
The nodes, dependents and calculations that we have in our two tables above, are actually treated as one item, which goes under the name master dependency directed graph. The dependency directed graph part of the name is due as we saw above, to the fact that each node contains a list of references to other nodes that depend on it, and this chain of dependencies takes the form of a directed graph.
The master part of the name is due to the fact that this graph contains all of the relevant information from a model--all of the possible calculations and all dependencies between them--as worked out during the rebuild phase.
Our MDDG for our simple form then, looks like this:
| Target | Type | Expression | Dependents |
|---|---|---|---|
a[1] |
node | c[1], d[1] |
|
b[1] |
node | c[1], d[1] |
|
c[1] |
calculate | a[1] * b[1] |
c[1]'s constraint |
c[1] |
constraint | c[1] <= 100 |
|
d[1] |
calculate | a[1] + b[1] |
d[1]'s constraint |
d[1] |
constraint | d[1] <= 20 |
With this 'master' graph, given information about one or more nodes that have changed, the dependency engine can work out which calculations would need to be performed. This process of performing calculations based on the relationships between nodes is called recalculation, and we'll look at that next.
We've seen how the rebuild phase recreates all of the dependencies for a particular model; once this list of dependencies is available, the recalculate phase can make use of the list to carry out its calculations.
Recall that we had this combination of instance and bind statements:
<xf:instance>
<instanceData xmlns="">
<a>10</a>
<b>10</b>
<c />
<d />
</instanceData>
</xf:instance>
<xf:bind nodeset="c" calculate="../a * ../b" constraint=". <= 100" />
<xf:bind nodeset="d" calculate="../a + ../b" constraint=". <= 20" />
and that after the rebuild phase, we'd have the following MDDG:
| Target | Type | Expression | Dependents |
|---|---|---|---|
a[1] |
node | c[1], d[1] |
|
b[1] |
node | c[1], d[1] |
|
c[1] |
calculate | a[1] * b[1] |
c[1]'s constraint |
c[1] |
constraint | c[1] <= 100 |
|
d[1] |
calculate | a[1] + b[1] |
d[1]'s constraint |
d[1] |
constraint | d[1] <= 20 |
We've already said that the whole purpose of having this 'master' graph is to ensure that the XForms processor doesn't carry out unnecessary calculations, so the next question is how is it used.
If you got the hang of the master graph that contains details of all calculations and dependencies in a model, then the idea of a smaller graph that contains only the relevant parts of the master, will be quite easy. For example, if a changes, we can see that it has dependents of c and d, and so create a smaller graph:
| Target | Type | Expression | Dependents |
|---|---|---|---|
c[1] |
calculate | a[1] * b[1] |
c[1]'s constraint |
d[1] |
calculate | a[1] + b[1] |
d[1]'s constraint |
To create this list the dependency-engine needs to know what data has changed; if a node is changed by the user (via a form control) or by xf:setvalue, then a reference to the node is recorded in a change list. This list of nodes is then used during the recalculate phase to create a pertinent dependency subgraph, which shows only those calculations from the 'master' list, that need to be performed.
To continue our example, note that the two calculations that were just added to the subgraph also have dependents:
| Target | Type | Expression | Dependents |
|---|---|---|---|
c[1] |
calculate | a[1] * b[1] |
c[1]'s constraint |
d[1] |
calculate | a[1] + b[1] |
d[1]'s constraint |
Having a dependent means that after c and d are recalculated there are further calculations that need to take place, and they too must be added to the subgraph:
| Target | Type | Expression | Dependents |
|---|---|---|---|
c[1] |
calculate | a[1] * b[1] |
c[1]'s constraint |
d[1] |
calculate | a[1] + b[1] |
d[1]'s constraint |
c[1] |
constraint | c[1] <= 100 |
|
d[1] |
constraint | d[1] <= 20 |
Once this list has been created, all recalculations in the list can be performed. The PDS is then discarded, since it will be recreated the next time recalculate occurs.
Often there will be situations where calculations in one model are dependent on data in another. For example, let's say we have a model that contains a list of actions that our user can perform:
<xf:model id="mdl-actions">
<xf:instance id="inst-actions">
<actions xmlns="">
<action id="add">Add</action>
<action id="delete">Delete</action>
</actions>
</xf:instance>
</xf:model>
We could now use this data to show a list of triggers to the user:
<xf:repeat nodeset="action">
<xf:trigger>
<xf:label ref="." />
</xf:trigger>
</xf:repeat>
Let's extend this so that each action contains a value that indicates whether the user can perform that action or not, and we set the relevance of the action based on that value:
<xf:model id="mdl-actions">
<xf:instance id="inst-actions">
<actions xmlns="">
<action id="add" allowed="1">Add</action>
<action id="delete" allowed="1">Delete</action>
</actions>
</xf:instance>
<xf:bind nodeset="action" relevant="boolean-from-string(@allowed)" />
</xf:model>
Now all we need to do to show or hide the trigger is bind it the action node (so that it is affected by the node's relevance):
<xf:repeat nodeset="action">
<xf:trigger ref=".">
<xf:label ref="." />
</xf:trigger>
</xf:repeat>
and then use setvalue to control whether a user can perform the action or not. For example, if the last item in a list is deleted, we might indicate that the 'delete action' is not available:
<xf:setvalue ref="action[@id='delete']/@allowed">0</xf:setvalue>
This is a very useful construct, but let's extend it now so that other data plays a role in determining what actions a user can perform. Let's say we have another model that provides some information about a user that has logged in to some system. This information would be provided dynamically, but to make it easier to follow, we've shown it here in an instance:
<xf:model id="mdl-login">
<xf:instance id="inst-login">
<user xmlns="">
<name>John Doe</name>
<role>admin</role>
</user>
</xf:instance>
</xf:model>
Now, let's add a bind statement to our previous instance that allows administrators to use delete:
<xf:bind nodeset="action[@id='delete']/@allowed" relevant="globalInstance('inst-login')/role[. = 'admin']" />
<xf:bind nodeset="action" relevant="boolean-from-string(@allowed)" />
</xf:model>
NOTE: We've used the formsPlayer globalInstance function, which allows references to be made to instances in different models. It's a fairly straightforward function, and no dependencies are created.
This should have the effect of setting the value of @allowed on the 'delete action' element based on whether our form user is an administrator or not. However, the key question here is when exactly is this calculation performed?
Recall from the recalculation phase that only those items that are in the change list will cause dependents to be recalculated. In other words, there is no 'recalculate all', except after a rebuild. In our example above, changes to any of the allowed attributes will cause a recalculation of the relevance of the corresponding action element, but changes to the value of globalInstance('inst-login')/role will not.
This obvious solution is to perform a rebuild on the 'actions model', whenever a rebuild is performed on the 'login model', for example, after the submission in the login model has completed:
<xf:model id="mdl-login">
<xf:instance id="inst-login">
<dummy xmlns="" />
</xf:instance>
<xf:submission action="..." method="..."
replace="instance" instance="inst-login"
>
<xf:rebuild ev:event="xforms-submit-done" model="mdl-actions" />
</xf:submission>
</xf:model>
However, there is an easier way, which is to put the relevance rule we added to the action model, into the login model; this means that the calculation will be performed as part of the normal course of events at the right time, whenever anything changes in the login model. Our finished mark-up is as follows:
<xf:model id="mdl-login">
<xf:instance id="inst-login">
<dummy xmlns="" />
</xf:instance>
<xf:submission action="..." method="..."
replace="instance" instance="inst-login"
/>
<xf:bind nodeset="globalInstance('inst-actions')/action[@id='delete']/@allowed" relevant="instance('inst-login')/role[. = 'admin']" />
</xf:model>
<xf:model id="mdl-actions">
<xf:instance id="inst-actions">
<actions xmlns="">
<action id="add" allowed="1">Add</action>
<action id="delete" allowed="1">Delete</action>
</actions>
</xf:instance>
<xf:bind nodeset="action" relevant="boolean-from-string(@allowed)" />
</xf:model>
What's interesting about this approach, is that the 'actions model' remains encapsulated; it deals only with whether an action can be performed or not, but has no 'knowledge' of the conditions that determine that. And from a programmer's perspective we're using bind literally to mean 'please add this calculation to the master dependency directed graph for this model'.
Selenium is an automated test tool from OpenQA that provides control over web applications running in a browser. Although it's normally used to test HTML or Ajax sites, we've built a number of hooks into formsPlayer so that Selenium can also be used to test XForms applications. This section explains how to make use of these extensions.
Before you install Selenium and start creating your own test suite for your XForms application, you might want to try it out. If you have formsPlayer installed already, direct Internet Explorer to the formsPlayer test suite. Next select TestXForms10SubmissionGetReplaceInstance in the left frame, and click Selected in the very right-hand frame. Selenium will start running the test, using the bottom frame to hold the actual XForms document to be tested. After some activity, the right-hand frame should show that 1 test has run, and that 2 commands have passed.
Now that you've seen Selenium in use on the formsPlayer server, we'll look at how you can use it for your own projects.
If you're not already using Selenium for other parts of your project, then the first step will be to obtain a copy of Selenium Core. The standard configuration is to place the Selenium files on the same server that is delivering your application, so that there are no cross-domain security problems. Once you have the download, it would be worth testing your set-up by running some of the core Selenium tests.
When Selenium runs it looks for a script called user-extensions.js which contains any custom extensions that are needed. This allows you to upgrade Selenium without losing any modifications that might have been made.
You can find the formsPlayer extensions in our Subversion repository:
<http://svn.x-port.net/svn/public/testsuite/selenium/core/scripts/user-extensions.js>
Copy this file to the selenium/core/scripts directory.
Now you should be ready to create some test-cases. A test in Selenium involves a normal HTML document and a 'driver' that describes some steps to take on that document. For example, we might want the test to act as if something was typed into a control, or the mouse clicked on a button.
The files that drive the tests are themselves HTML, and use a simple table layout to define them. We'll leave a full description of how this works to the Selenium documentation, but as a way of getting started quickly, take a look at the 'driver files' that are available from our Subversion repository:
<http://svn.x-port.net/svn/public/testsuite/selenium/tests>
For example, we have:
The naming convention is probably obvious, but these tests are for:
Remember that these files are 'driver' files--i.e., they are the instructions on how to control the test and what to check for, but they do not contain the actual functionality. In the case of these test files, we've kept them separate, and the tests being controlled come from the samples area. For example, samples that show how to use XForms 1.0 submission are here:
<http://svn.x-port.net/svn/public/samples/specifications/xforms/1.0/submission>
The file used in the example at the beginning, which tested submission with replace="instance", is in this directory, and is called submission-get-replace-instance.html.
To control a group of tests, a further HTML file is created that contains references to all of the necessary driver files. TestFormsPlayer.html is the file that we saw above, that runs all of the formsPlayer tests (available in the same Subversion directory mentioned above), but other test suites could be created to test narrower ranges of functionality, such as only XForms 1.0 features, or only submission.
To execute the tests we load the core test runner, and provide it with the location of a test suite. For example, to run the formsPlayer test suite directly from the Subversion server (as we saw earlier), we can use the following URL:
<http://svn.x-port.net/svn/public/testsuite/selenium/core/TestRunner.html?test=../tests/TestFormsPlayer.html>
Once Selenium has loaded, a frameset is displayed that contains:
To run all of the tests in the test suite, in one go, select 'All' from the test results panel, otherwise click on 'Selected' if you want to run tests one at a time.
formsPlayer provides a number of utilities which enable us to build complex web applications quickly. One of these is a framework for creating side-bars, footer bars, and toolbars that are driven by XHTML and XForms. These bars sit in the browser just like any that are written in C++, but are as easy to create as a web-page.
In this section we will look at some of the different browser extensions that can be created with formsPlayer.
One class of extensions that most browsers support is the addition of panels that a user can have open whilst they are browsing. For example, you might create a side-bar that allows searches to be carried out, with the advantage over a normal search in the browser window that you don't lose your list when stepping through the results.
There are many terms used for these kinds of panels. We'll take the following as our definitions:
Any XHTML document can be used as a side-bar, footer bar, toolbar or explorer bar. This makes it very easy to build bars that extend your browser, and all that we need to do is to let the browser know where to find the XHTML documents. We do this not be creating standard desktop installers, but by creating XForms that call the Soft-bar Installer API. This API provides functions to add and remove bars.
This tutorial will show how to use the API to create an installer, and to illustrate the technique we'll place the del.icio.us bookmark form created in the Introduction to XForms in a side-bar. In order to illustrate how any document can be used as a 'soft bar', we won't make any changes at all to the bookmark form, with all the work being done by the installer.
The sample and source code are available from here.
Tutorial reviewed on 2006-12-07, by MB.
TODO: Incorporate material from A del.icio.us Link Manager Written in XForms.
To create a side-bar we need two things; an XHTML document that will provide the functionality of the side-bar, and an installer that will wire the side-bar into the browser. The XHTML document we will use is the del.icio.us bookmarking form that we create in the Introduction to XForms. We'll use this partly because it's a useful form, and partly to show that any XHTML document can be used as a side-bar.
The installer will have to be created from scratch, but since it is just a normal XForm we can use the usual template as our starting-point. We'll begin by setting the document's title:
<title>Soft-bar Installer</title>
</head>
To access any external function library in XForms you need to provide a reference to a namespace that identifies the library, and you need to indicate the functions you want to access in the functions attribute on the model. So the first step is to add the following namespace to the top of the document:
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:sb="urn:formsplayer.com/softbars"
>
<head>
Now add a model which will contain the data about the soft-bar we will be installing, and reference the functions that will be called to do the adding and removing:
<title>Soft-bar Installer</title>
<xf:model id="m-soft-bars" functions="sb:GenerateBar sb:RemoveBar">
...
</xf:model>
</head>
To make it easy to reuse this installer form for other bars, we'll put all information that is specific to our del.icio.us side-bar into an external XML document. Create a document called delicious-sidebar.xml that contains the following:
<bar retval="0">
<type>2</type>
<screenshot>http://static.flickr.com/142/319487219_f598c70667.jpg</screenshot>
<internalName>fpdelicioussidebar</internalName>
<name>del.icio.us Sidebar</name>
<description>
The del.icio.us Sidebar allows you to quickly add a bookmark to del.icio.us
for a document that you are viewing in the main browser window.
</description>
<uri>http://svn.x-port.net/svn/public/samples/del.icio.us/howto.html</uri>
</bar>
type, internalName, name and uri correspond to the parameters needed by sb:GenerateBar, whilst screenshot and description are used in the form, as we'll see in a moment.
To make the configuration parameters available to the form, we need to load it into an instance, so next add the following mark-up to the model:
<xf:model id="m-soft-bars" functions="sb:GenerateBar sb:RemoveBar">
<xf:instance src="delicious-sidebar.xml" />
</xf:model>
Part of the information in the configuration file is for the calls to the Soft-bar API, and some of it is just to improve the appearance of our form. Add the following controls to the body element, which will give the user some information about the soft-bar being added:
<body>
<h1><xf:output ref="name" /></h1>
<xf:output
value="concat('
<img src="',
screenshot,
'" />'
)"
/>
<div><xf:output ref="description" /></div>
</body>If you save and refresh the form, you should see a heading of the name of the soft-bar, a screenshot (if there is one), and a description of what the bar does.
The next piece we need to add is the code to install the bar. For this we have to use the sb:GenerateBar method, which is available to us because we specified the library in the functions attribute. We have to call the function from an XPath expression, and since we want the returned value (so that we know if it succeeded or not) we'll use the setvalue action. We'll put the setvalue inside an action handler that is listening for the event my-install-bar; place the following in the model element:
<xf:instance src="delicious-sidebar.xml" />
<xf:action ev:event="my-install-bar">
<xf:setvalue
ref="@retval"
value="sb:GenerateBar(
string(../type),
string(../internalName),
string(../name),
string(../uri)
)"
/>
</xf:action>
</xf:model>
Once the function returns we'll be left with either "Success" or an error message in the retval attribute. We can make use of this by following the setvalue action handler with a message:
/>
<xf:message level="modal">
'<xf:output ref="name" />'
has
<xf:output value="
if(
@retval = 'Success',
' been added. You will need to open a new browser
window to see the change.',
concat(
' failed to install. The error is "',
@retval,
'".'
)
)"
/>
</xf:message>
</xf:action>
The effect of this is to always show a message after installing, but the text of the message will depend on whether the installation was successful or not--with the if function being used to determine which.
To allow the user to install the bar, all we need do now is provide a button that will invoke the my-install-bar event. We'll put it after the other controls:
<div><xf:output ref="description" /></div>
<xf:trigger>
<xf:label>Install</xf:label>
<xf:dispatch ev:event="DOMActivate"
name="my-install-bar" target="m-soft-bars"
/>
</xf:trigger>
</body>
To complete the form we need to add an event handler and button for removing the soft-bar. The handler will use the sb:RemoveBar function and pass it the internal name of the bar. Add the following to the model:
</xf:action>
<xf:action ev:event="my-remove-bar">
<xf:setvalue
ref="@retval"
value="sb:RemoveBar(string(../internalName))"
/>
<xf:message level="modal">
'<xf:output ref="name" />'
has
<xf:output value="
if(
@retval = 'Success',
' been removed. You will need to open a new browser
window to see the change.',
concat(
' failed to be removed. The error is "',
@retval,
'".'
)
)"
/>
</xf:message>
</xf:action>
</xf:model>
To invoke the handler we need another button:
</xf:trigger>
<xf:trigger>
<xf:label>Remove</xf:label>
<xf:dispatch ev:event="DOMActivate"
name="my-remove-bar" target="m-soft-bars"
/>
</xf:trigger>
</body>
In this example we'll show how to create a topbar that can be used to search Flickr for images that have a specific tag.
A common use for browser toolbars is to make it easy to search for some information and get the results directly in the main browser window. All the main search engines have such toolbars, usually created using C++ and distributed via standard installers. But using the formsPlayer soft-bar library, we can create handy browser extensions for Internet Explorer using barely a few lines of XForms. To illustrate, we'll create a toolbar that uses the recently published API documentation for the popular Prototype Javascript Framework.
The documentation is pretty straightforward. The page for the Element object is:
http://prototypejs.org/api/element
whilst the page for the Form object is:
http://prototypejs.org/api/form
Methods and properties on an object are also available directly, for example, the method Element.addClassName is available at:
http://prototypejs.org/api/element/addclassname
A simple form to access this information need be no more than one control and an action handler, so we'll begin with an input control to collect the topic of interest to the user:
<body>
<xf:input ref="topic">
<xf:label>Topic:</xf:label>
</xf:input>
<body>
This control allows the user to enter a search term, which is then stored in a node called topic. Once the user has entered a topic to search for we want to use the value in topic as part of the URL, just as we illustrated above. We need to make one small change though, and that is we'd like a user entering element.addclassname to be taken to:
http://prototypejs.org/api/element/addclassname
To achieve this, we'll replace any periods with '/', and then add the result to the end of the basic URL for the documentation. The XPath for this is simply:
concat('http://prototypejs.org/api/', translate(topic, '.', '/'))
Now we know how to work out the URL we want, how do we make use of it?
load actionXForms provides an action handler called load that will navigate to a URL, just like <a> in HTML. load is much more powerful though, since the URL is not limited to being a string of text; it can be created from other data that is available in the XForms model. But more than that, since the navigation is tied to an action handler and not a piece of the user interface--as the anchor tag is in HTML--the features that load provides can be used in a much wider range of situations.
An example of using load with just a straightforward string literal for the URL might be:
<xf:load resource="http://prototypejs.org/api/element" />
A typical use might be to create a trigger that navigates to a page, in just the same way that <a> does; the following are in fact directly equivalent:
<xf:trigger appearance="minimal"> <xf:label>element</xf:label> <xf:load ev:event="DOMActivate" resource="http://prototypejs.org/api/element" /> </xf:trigger> <a href="http://prototypejs.org/api/element">element</>
There is no equivalent in HTML for the more advanced type of navigation, where the URL is created from data in the XForms model, although the following probably conveys the point:
<xf:trigger appearance="minimal"> <xf:label>View topic</xf:label> <xf:load ev:event="DOMActivate"> <xf:resource value="concat('http://prototypejs.org/api/', topic)" /> </xf:load> </xf:trigger> <a href="http://prototypejs.org/api/{topic}">View topic</>
DOMActivate with an inputNow that we know how to use load, we need to link it to the data entered into the input control. We could just use the trigger that we created above to illustrate the use of load, but we actually don't need to, since pressing [ENTER] in an input control will generate its own DOMActivate event. We therefore only need to add our action handler to the input control--with a minor modification to our XPath statement to take into account that topic is now our evaluation context--and we are able to perform everything we have just discussed:
<body>
<xf:input ref="topic">
<xf:label>Topic:</xf:label>
<xf:load ev:event="DOMActivate">
<xf:resource value="concat('http://prototypejs.org/api/', translate(., '.', '/'))" />
</xf:load>
</xf:input>
<body>
This is our complete form, and in just a few lines of XForms we are able to take a value from our users, and when they press [ENTER], use that value to navigate to a page within the Prototype API documentation. Now we need to turn this form into a toolbar.
As this simple form stands, if the user entered a topic to look for, and then pressed [ENTER], the form would be replaced with the correct page from the Prototype documentation. Whilst this is not ideal behaviour when the form is running in the main browser window, it's just what we want when the form is running as a soft-bar. This is because any navigation that takes place in the soft-bar--whether it's a toolbar, side-bar, footer-bar or explorer-bar--is 'bounced' through to the main window. As a result, it's very quick and easy to create toolbars such as this one, that search Google, Flickr, IMDB, Wikipedia, and so on, and show the results in the main window.
To complete turning our form into a toolbar, we need to indicate its title as it will appear in the toolbar:
<head>
<title>Prototype API Documentation Search</title>
<meta name="dc:creator" content="Mark Birbeck" />
and set the toolbar's starting, maximum and minimum dimensions:
<meta name="dc:creator" content="Mark Birbeck" />
<meta name="maxwidth" content="300" />
<meta name="maxheight" content="21" />
<meta name="minwidth" content="300" />
<meta name="minheight" content="21" />
<meta name="defwidth" content="300" />
<meta name="defheight" content="21" />
<style type="text/css">
Installation is extremely straightforward, and follows the same pattern described in HOWTO: Creating a del.icio.us side-bar and installer. If you want to install the toolbar you can do so from here. (You'll need formsPlayer, but this form will install it automatically.) Also, if you'd like to get the code and use it to create your own toolbars, it is available from here.
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.
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:
whilst our analogue clock widget might look like this:
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:
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.
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.
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;" />
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.
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>
xf:repeat to show a list of controlsSince 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>
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>
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.
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:
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);
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;
}
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.
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;" />
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.
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.
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>
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.
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.
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>
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.
setValue methodTo 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>
setValue methodThe 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://libxh-apps.googlecode.com/svn/trunk/tutorials/custom-controls/map/.
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:
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.
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:
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.
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>
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.
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>
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/.
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" />
This section aims to build a standard collection of CSS style rules for use with XForms processors.
The purpose of the collection is twofold, with the first being to define how exactly all of the pseudo-elements and classes referenced in the XForms spec should behave.
The second goal is to provide workarounds for processors that do not support the full range of CSS features, such as pseudo-elements and classes; the following list uses 'normal' classes, but with an agreed naming convention. (The approach was agreed by the W3C's XForms Working Group.) In this situation the stylesheets can be used by authors with their own forms, but can also be distributed by implementers with their processors.
Each rule is given its own page so that comments can be attached. The most up-to-date collection of the rules--along with support files such as images--is always available via Subversion at:
http://svn.x-port.net/svn/public/xformswg/styling/
The data entry areas for input and secret are simple rectangles:
xf\:input pe-value,
xf\:secret pe-value
{
display : inline-block;
border-top : 2px inset;
border-right : 2px inset;
border-bottom : 2px inset;
border-left : 2px inset;
width : 250px;
}The ::enabled and ::disabled pseudo-classes indicate the status of the data that some control is bound to, based on the relevant Model Item Property. If a control is disabled then the default is for it not to be rendered:
.pc-disabled
{
display : none;
}
The :focus pseudo-class indicates that a control currently has focus.
.pc-focus
{
border: 1px lightgrey solid;
}
An element has the :hover pseudo-class when under the mouse pointer.
.pc-hover
{
background-color: lightblue;
}
Action handlers are not rendered, by default:
xf\:action,
xf\:delete,
xf\:insert,
xf\:rebuild,
xf\:recalculate,
xf\:refresh,
xf\:revalidate,
xf\:send,
xf\:setvalue,
xf\:toggle
{
display : none;
}
The alert element has no state of its own, but it can be styled based on the validity of its parent element. The default style is to hide the element when the parent control is valid:
.pc-valid xf\:alert
{
display : none;
}
The specific rendering of an item depends on whether it is ::selected or ::deselected and whether its parent is a select or a select1. However, there are some common style values that are set here (with the remainder set in the following sections):
xf\:item
{
width : 100px;
display : block;
background-repeat : no-repeat;
background-position : right;
}
The default appearance of an option (i.e., an xf:item) within a select control is that of a check-box:
xf\:select .pc-selected
{
background-image: url(check-selected.png);
}
xf\:select .pc-deselected
{
background-image: url(check-deselected.png);
}
The images used are:
check-selected.png:check-deselected.png:
![]()
The default styling of the options in a select1 control is to look like a radio button:
xf\:select1 .pc-selected
{
background-image: url(radio-selected.png);
}
xf\:select1 .pc-deselected
{
background-image: url(radio-deselected.png);
}
The images used are:
radio-selected.png:radio-deselected.png:
![]()
The default style for a trigger is to look like a button:
xf\:trigger
{
padding : 0 0 0 0;
margin-right : 10px;
border-top : 2px outset #e0e0e0;
border-right : 2px outset #191919;
border-bottom : 2px outset #191919;
border-left : 2px outset #e0e0e0;
background-color : #d0d0d0;
}
xf\:trigger xf\:label
{
display : inline-block;
padding-top : 3px;
padding-left : 5px;
padding-bottom : 3px;
padding-right : 5px;
margin : 0 0 0 0;
border : none;
background-color : #d0d0d0;
width : auto;
font-family : arial;
cursor : pointer;
}
The Soft-bar Installer API is a set of functions available to an XForms document which allow bars to be added and removed from the browser.
The namespace for the library is:
urn:formsplayer.com/softbars
Functions in the library can be made available to XPath expressions like this:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:sb="urn:formsplayer.com/softbars" > <head> . . . <xf:model functions="sb:GenerateBar sb:RemoveBar"> ... </xf:model> </head> . . . </html>sb:GenerateBar
The
sb:GenerateBarfunction will add a new bar to the list of bars available to the browser.The parameters are:
- type
- The type of bar to add:
- 1 = toolbar;
- 2 = sidebar;
- 3 = footer-bar;
- 4 = explorer bar.
- internal name
- A name by which you can identify the bar. It's important that this is unique since it will be used internally by the browser to link the bar to any menus, and is also used to identify the bar when calling
sb:RemoveBarto remove it. This should be completely alphabetic.- menu entry
- A name for the bar that will be used in the browser menu.
- URL
- The location of the bar. This can be on the internet, which does make it very easy to add new functionality to the bar without requiring a download.
The return value will be "Success" if successful, otherwise an error description.
Example
A typical use would be to invoke the function, and then show a message. The message could indicate success or failure:
<xf:action ev:event="my-install-bar"> <xf:setvalue ref="@retval" value="sb:GenerateBar( string(../type), string(../internalName), string(../name), string(../uri) )" /> <xf:message level="modal"> '<xf:output ref="name" />' has <xf:output value=" if( @retval = 'Success', ' been added. You will need to open a new browser window to see the change.', concat(' failed to install. The error is "', @retval, '".') )" /> </xf:message> </xf:action>sb:RemoveBar
The
sb:RemoveBarmethod will remove a previous installed soft-bar. There is only one parameter:
- internal name
- The internal name used when the bar was created with
sb:GenerateBar.
The return value will be "Success" if successful, otherwise an error description.
Example
A typical use would be to invoke the function, and then show a message. The message could indicate success or failure:
<xf:action ev:event="my-remove-bar"> <xf:setvalue ref="@retval" value="sb:RemoveBar(string(../internalName))" /> <xf:message level="modal"> '<xf:output ref="name" />' has <xf:output value=" if( @retval = 'Success', ' been removed. You will need to open a new browser window to see the change.', concat(' failed to be removed. The error is "', @retval, '".') )" /> </xf:message> </xf:action>