Validating XForm input with XML Schema and extension function

Alex Sansom's picture

Here's another experiment from my 'tests' forms directory. It shows a few XForms features including the use of an extension function and inline (not that it has to be) XML Schema for validation.

The form I was writing used an XML Schema to dictate the strucure of the instance data. Within the schema there were also restrictions on some of the patterns of the XML data content that's allowed to be entered to be considered valid.

The particular piece of XML data that inspired me to create my test form defined a sales representative's identity number. This data was defined as a sequence of either 3 digits or a single uppercase character followed by 2 digits. The schema definition was:

  1.     ...
  2.     <xsd:pattern value="[\dA-Z][\d]{2}" />
  3.     ...

This means that if a user of the form enters 'R12 it's a valid value but 'r12' is not. The pattern defines (by their absence) lowercase characters in the value to be invalid. Ideally I'd be able to alter the pattern defined to allow lowercase chars but it's not my schema and so can't.

To get around this case sensitivity issue I decided I'd automatically uppercase the value that a user enters. I know that there's no straightforward toUpperCase() XPath/XForms function available, there is translate() but that's not the greatest.

I decided to use an XForms extension function to handle the uppercasing of the value. The plan here was to call a function that wraps around javascript String.toUpperCase() using the value the user has entered as input. This means that I won't need to worry that if the user enters their representative number as 'r12' that they'll be told that it's invalid as it'll automatically be uppercased to 'R12' for them and found to be valid.

To prove all this could work I created my test form (attached to this post at the bottom of the page).

The first thing I did was to create a simplified XML Schema that defines a small XML instance with a couple of nodes that use the same pattern as the representative number. I take advantage of being able to include this schema inline in the form:

  1.     ...
  2.     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="inline">
  3.         <xsd:element name="data">
  4.             <xsd:complexType>
  5.                 <xsd:all>
  6.                     <xsd:element name="representative_number_1" type="RepNumber" />
  7.                     <xsd:element name="representative_number_2" type="RepNumber" />
  8.                 </xsd:all>
  9.             </xsd:complexType>
  10.         </xsd:element>
  11.         <xsd:simpleType name="RepNumber">
  12.             <xsd:restriction base="xsd:string">
  13.                 <xsd:pattern value="[\dA-Z][\d]{2}" />
  14.             </xsd:restriction>
  15.         </xsd:simpleType>
  16.     </xsd:schema>
  17.     ...

Now I've got the schema that'll validate the users data, I create the XForms model, tell it to use the schema, with the xf:model's @schema attribute, and create the small set of instance data as defined by the schema:

  1.     ...
  2.     <xf:model id="mdl-test" schema="#inline">
  3.         <xf:instance id="inst-data">
  4.             <data xmlns="">
  5.                 <representative_number_1>r12</representative_number_1>
  6.                 <representative_number_2>r34</representative_number_2>
  7.             </data>
  8.         </xf:instance>
  9.         ...
  10.     </xf:model>
  11.     ...

I also need to add a couple of form controls, with messages to show when they are invalid, to bind to the instance data via their @ref attributes:

  1.     ...
  2.     <xf:group ref="/data">
  3.         <xf:input ref="representative_number_1">
  4.             <xf:label>Rep. number 1: </xf:label>
  5.             <xf:alert>Invalid value!</xf:alert>
  6.         </xf:input>
  7.         <xf:input ref="representative_number_2">
  8.             <xf:label>Rep. number 2: </xf:label>
  9.             <xf:alert>Invalid value!</xf:alert>
  10.         </xf:input>
  11.     </xf:group>
  12.     ...

So there's the schema, the XForms model with instance data and form controls that bind to the instance data. What's missing is my extension function and a way of invoking it on the instance data.

First the extension function, inserted in the document <head>:

  1.     ...
  2.     <script type="text/javascript">
  3.         function ext_toUpperCase(s)
  4.         {
  5.             return s.toUpperCase();
  6.         }
  7.     </script>
  8.     ...

As you can see, all it does it call the javascript toUpperCase() on the input and return it. Now I need to call this function, using the instance data as the input to it. This is done using an <xf:bind> element with the @calculate model item property in the forms' model:

  1.     ...
  2.     <xf:model ...>
  3.         ...
  4.         <xf:bind nodeset="/data">
  5.             <xf:bind
  6.                 nodeset="representative_number_1"
  7.                 readonly="false()"
  8.                 calculate="normalize-space(js:ext_toUpperCase(string(.)))"
  9.             />
  10.         </xf:bind>
  11.     </xf:model>
  12.     ...

The extension function is called using the 'js:' namespace prefix (defined on the <html> element - take a look at the source). The parameter passed to my function is converted to a string type otherwise it will be a nodeset (and will generate errors). I pass the result of my function to XPath's normalize-space() so that any extra beginning or trailing whitespace is removed from the value as that will interfere with the validation of the instance data.

In the xf:bind element I've used 'representative_number_1' as the value of the @nodeset attribute so that only the first of the two instance data nodes has the extension function called on it. I can then see the diffent results on the nodes where the function is called and where it isn't. You can change the 'representative_number_1' value to '*' so that both nodes use the function.

That's pretty much it, apart from a bit of CSS to adjust the layout of the form controls and style their invalid messages, the contents of their <xf:alert> elements.

This form was developed for use with the ubiquity formsPlayer XForms engine so you'll need that installed if you want to run the attached form in your browser.

AttachmentSize
validation-with-schema-and-extension-function.html3.39 KB

Post new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.