Leigh L Klotz Jr photo

XForms: MVC in the Browser

HTML forms are the basis of interaction on the web, and yet are lacking many features we expect from user interface systems. For enterprise designers in particular, the Model-View-Controller (MVC) architecture separates data, logic, and presentation, yet HTML combines the three.

Today I'm pleased to introduce you to XForms, which is a way to author forms and gain a high level of expressive power. We use XForms in DocuShare, in our Wikis, Weblogs, Content Rules, and Content Repository administrative pages, and we've used XForms in a number of other Xerox products. I am co-chair of the World-Wide Web Consortium XForms Working Group.

I'll show you XForms working in DocuShare, on this server, and show how you can use it on your own HTTP server of any type.

Let's consider this (simplified) HTML4 form:

The presentation (labels, layout) is intermixed with the data definitions (the name attributes), and the server-side interactions (form submission) appears in HTML body, which is the presentation layer. There is no central place where the data is defined, and the model for data is very weak: a single set of names and values, with rules defining what happens on different types of controls with the same name. The labels are sometimes in the text, and sometimes intermixed in the values, as with the submit control.

Such a hodgepodge is what we've been living with on the web for years and years, and in fact whole legions of young people have grown up knowing nothing but this style of programming, and their perceptions have been warped much like those of a previous generation raised on BASIC.

In 1979, Trygve Reenskaug, Jim Althoff, and Adele Goldberg developed the Model-View-Controller architecture as part of Xerox PARC's work on SmallTalk. If we look at the web world through the lens of MVC, the browser serves as the controller, and HTML lets authors write flexible views, but the data is often nebulous, and is closely tied to the controls, and all the failings we saw above become obvious, as does the solution: we need to separate the data from the logic and the presentation. (If you want to know more about MVC, make a quick trip to the Wikipedia article or one of the other resources at the end of this post.)

In 1998, a group of people -- some from Xerox and other companies in Palo Alto, from the Center for Mathematics and Computer Science in Amsterdam, and from web design companies around the world -- got together to design an MVC architecture for HTML. The design was to be based on the recent view of HTML as an XML application, and so was called XForms.

XForms is designed to offer the native facilities of HTML and browsers, but in a way that separates the data from the presentation. As XForms has grown and become a standard (a World-Wide Consortium Recommendation), HTML has evolved as well, and JavaScript has gained prominence. XForms still offers a way to access the native browser facilities and keeps true to the MVC paradigm.

XForms also offers an easy path to Roy Fielding's REST architectural style, where the URL (URI) and its operations are considered paramount, and careful treatment of the data requires a way to interact with web servers in ways more subtle than the "replace the whole page" option offered by HTML4.

In XForms, we separate the data and the presentation, and make use of HTML's underlying facilities, and those of CSS, where appropriate, and add new facilities where they are not. The basic model of operation is exactly as described in the MVC Wikipedia article:

So, let's first take a look at the most obvious way in which XForms offers MVC and separates the data from the presentation. The data that was implicit in HTML4 is explicit in XForms, and is usually moved to the HTML head, or even to the server. Also, XForms separates the labels from the form controls in a way that's great for web accessibility (A11Y).

Note first that we've moved two things into the head: the server interaction is expressed in the submission element, and the data is made explicit in the instance element. In the body, each of the inputs refers to a part of the instance (we'll call each of these data items a node), and each of the inputs has an explicit label. Finally, submit gets its own new element, with a label just like input. (We'll leave to CSS the task of arranging these form controls on the page.)

The submission element has an id attribute on it, so it can be found by the corresponding submit. The submission does its submission via HTTP POST, and the data that gets sent is the initial instance data, filled out by the various inputs with which the user has interacted. The submission says to replace all, so just like the HTML4 page example, we'll see the page replaced after the submission.

Now let's add some data type information. Name is a string, so we'll leave it alone, but price and quantity are numbers. In fact, we'll go further and say that price is a decimal, and quantity is a positive integer.

In XForms, we can use an external XML Schema to enforce data constraints on submitted instance data, but if there's no pre-existing schema description, we can use the XForms bind element to add datatypes directly. The XForms processor (controller) makes use of the data type information to display the data in a way suitable for the user, and to enforce other behaviors associated with the specified data type.

In order to add these types, we need to add just these two lines to the model:

Now the XForms processor will enforce these type definitions, and won't submit instance data that violates them. Most processors will also display helpful marks that show when fields are invalid.

If we'd like to add more constraints, we can do so with the constraint attribute. For example, there's no pre-defined positive-decimal type, so we can use an expression in the constraint attribute. (Note that we have to escape the greater-than sign.)

Let's now mark all three of these items as required, so they will be non-blank in order to be submitted, and will be marked as required. (Exactly how it's marked is up to the XForms processor and the CSS used, so designers have control over this aspect, but form authors express the basic concept in the same way, in another example of the separation of concerns offered by XForms.)

Note that we had to say "true()" because the value of the required attribute is an evaluated expression. This makes it trivial to have nodes which are required or not based on dynamic conditions, and the XForms processor takes care of satisfying these requirements dynamically. The author just has to express them.

Now let's look at our page:

How can we put this into use? One of the easiest ways is to use an open-source product from AgenceXML, which is a set of files you put on your web server or local file system and activate with a directive at the top of the file.

I've already placed a copy of the AgenceXML package on this server, and so in order to use it, I just place this directive at the top of the file:

There are a few changes necessary in the XHTML+XForms file as well, mostly because the W3C designed its XML stack of applications, including XHTML and XForms, to use a concept called namespaces where each vocabulary has its own URL. You can use name prefixes on the elements, or you can use a general declaration to switch namespaces. With the AgenceXML implementation of XForms, two approaches work: either using XHTML as the default namespace (unprefixed) and a prefix on all XForms elements, or switching back and forth and using a namespace declaration when you switch vocabularies. We'll use the latter approach here because it's a little easier to read, but if you switch frequently (i.e., if you embed XHTML inside XForms inside XHTML) then you may prefer to use the prefixes always. We'll also end the use of namespaces with xmlns="" once we get to the instance data, because it's our own data vocabulary, and is neither XForms nor XHTML data.

Here's how the example above changes when used with the AgenceXML implementation. Aside from a few "xmlns" added along with the attributes, we add an XForms group element around the UI. The group element is like an HTML div or fieldset, and can be styled with CSS, and can also hold various defaults for the content inside it. It's useful, but for now just think of is the pivot-point where we switch into XForms presentation in the HTML body. The three lines with the <? at the front are the magic ones needed to instruct the browser to fetch the AgenceXML files and transform the rest of the document from XHTML+XForms into plain old HTML4+JavaScript:

At this point we have a working client-side example, but note there is no CGI script or service at the server end of the "/buy.cgi" URI, so if you press the submit button, you won't get any visible results.

How can we at least let the user know something went wrong? In HTML4, if you get an HTTP 404 error, the browser gives you a page right away, but in XForms, the form author gets to decide what to do. The notification is done via events, and they're the same events you may be used to in HTML with JavaScript, and in fact they interoperate. XForms provides a few events of its own, and their names start with "xforms-", and finally XForms processors generally implement a few generic events that are defined in the DOM Events specification but not often used. (For example, XForms authors tend to use DOMActivate instead of click so that their actions can be triggered on Enter Key, not just on a mouse click.)

So, in XML Events, which are just an XML view on to DOM Events, there is an event name, and a target, among other features. In the XForms submission case, the target of the event is the XForms submission element, and the two events used are xforms-submit-done and xforms-submit-error. Normally they do nothing, but a form author can listen for the events and specify an action to perform.

The event has some context information we can use to read a failed response code, or to investigate why the submission didn't go forward at all (perhaps a required field was empty), but we'll ignore these abilities for now and just pop up a message using the XForms message element:

XML Events has its own namespace, which we'll need to declare at the top of the file:

Here's a copy of the page to try: buy-test1

Our form does what we said it does: it lets you input values and it gets errors when you try to submit, but it isn't very friendly or pretty.

So let's use some CSS to make it lay out better. The CSS stylesheet block-form.css defines a few classes we can use on group:

We'll put a block-form-section group around each part of our layout that we want in a separate block, and it will make a two-column layout of the form label and value and any associated icons (required, invalid, etc). A full CSS tutorial is beyond the scope of this document, so we'll just describe a few things about the way AgenceXML XForms works with CSS along the way.

Try the page at buy-test2

(By the way, View Source will work in your browser. In some browsers, if you have a View Selection Source you can also see the "transformed" output to see what AgenceXML has converted your XForms markup into.)

Let's suppose we also wanted to try just getting a price quote. By Roy Fielding's REST principles, if the RFQ doesn't have any side effects (such as committing to buy something), and it's just a price lookup, we should use an HTTP GET. Let's use a submission which serializes the XML data as an HTTP GET request, using application/x-www-form-urlencoded serialization, and which retrieves the price. (That's the form of HTTP GET request where there request URI has a question mark followed by names and values.) Add this to the model:

Note that it replaces the instance, not the whole page!

We'll also change it so that that price is no longer settable by the user, and isn't required (since it won't be required when we do the RFQ):

We need some way to trigger the quote, and we could do it on a DOMFocusOut or xforms-value-changed event on the "name" input control, but since our user might not tab off the input control, let's put in an explicit button. XForms calls the button control triggers because the name is independent of mode: a voice browser or screen reader doesn't have buttons, after all.

Add this before the submit control:

The live version of this form operates with a canned quote file, canned-quote.xml, instead of a real quote service, so you'll always get back the same results. This is a limitation of our demo environment and what server-side scripts are allowed.

Here's the whole form to try out. buy-test3

Note also that this example starts off with "peas" and quantity "1". We did that just by changing the initial data in the instance:

That makes it work better with our limited back-end script, but of course you can use your input control and change the value.

The ability to specify initial data in this way is fully generic, and in fact has more power as we'll see in a bit.

Let's move the initial data out of the instance and into a separate file; since there's a web server between the browser and the file system, you can see how this lets us initialize the data to something calculated by the server by a script. It could be localized, or it could be data that's time-dependent, such as stock quotes.

If you need to do the same thing with more inputs to the process, you can use a submission element with replace="instance" and obtain instance data at any time, just by filling in the instance data that gets submitted (by user or by a calculation, or from some other previous source), and either using a submit button or triggering a submission programmatically by sending an xforms-submit event directly to the submission element. (That was a lot of flexibility expressed in just one sentence, so don't worry if you didn't follow it all. We'll see some of these examples in later installments in this series.)

So, to close, let's take a look at our page again: buy-test4. With the above updates, there is no visible change, but if you do View Source in your browser once you follow the link, you'll see that we have indeed externalized the initial data. Depending on your browser, in the View Source you may see the initial-order.xml highlighted as a hyperlink so you can click on it and see the data.

That's all for this episode; please leave your comments, or contact me directly, and I'll answer your questions in a follow-up.


References


Xerox DocuShare and Web Technologies
Copyright © 2011 Xerox Corporation. All Rights Reserved.