Creating Applications: In-Page Updates

One fashionable avenue in Web application design has been that of updating Web pages in applications without having to refresh the entire page every time an action is performed. Together with some JavaScript support in the browser, XSLForms also provides some functionality for such "in-page" or "live" updates.

Consider the addition of a comment field to our application. Here is how the HTML code might look:

<div template:element="item">
<p>
Some item: <input template:attribute="value" name="{template:this-attribute()}" type="text" value="{$this-value}" />
<input name="remove={template:this-element()}" type="submit" value="Remove" />
</p>
<p>
Item type:
<select template:element="type" name="{template:list-attribute('type-enum', 'value')}" multiple="multiple">
<option template:element="type-enum" template:expr="@value-is-set" template:expr-attr="selected"
template:value="@value" value="{@value}" />
</select>
</p>
<p template:element="options">
<span template:element="comment">Comment:
<textarea template:attribute="value" name="{template:this-attribute()}" cols="40" rows="3">
<span template:value="$this-value" template:effect="replace">Some comment</span>
</textarea>
</span>
</p>
<p>
Itself containing more items:
</p>
<p template:element="subitem">
Sub-item: <input template:attribute="subvalue" name="{template:this-attribute()}" type="text" value="{$this-value}" />
<input name="remove2={template:this-element()}" type="submit" value="Remove" />
</p>
<p>
<input name="add2={template:this-element()}" type="submit" value="Add subitem" />
</p>
</div>

The newly-added textarea field will not be presented in the application in its current state; this is due to the lack of any options or comment elements manipulated by the application, and such template changes are actually quite safe to make. So, we must now do some additional work to add such options and comment elements in our application.

One approach is to extend our transformation which adds the different type values so that these new elements are introduced as well. In the Web resource, we can make the following change:

    transform_resources = {
"types" : ["structure_multivalue_types.xsl", "structure_comments.xsl"]
}

What this does is to state that when we carry out the types transformation, two stylesheets are employed, one before the other, such that the type values are first added using the first stylesheet (and the additional reference document containing the type values) and that the comments are then added using the second stylesheet.

This new stylesheet works according to the following principles:

  1. Descend into the form data structure, copying all elements, attributes and text that the stylesheet is not programmed to recognise.
  2. When encountering an item element (which the stylesheet is programmed to recognise), do the following:
    1. Copy the element "skeleton" and its attributes so that the value attribute is retained.
    2. Produce a new options element and process it.
  3. When processing a new options element, do the following:
    1. Inside this new options element, investigate the values associated with the type element.
    2. If any of the selected type values is "Personal", make a new comment element, then add any attributes that may be found on existing comment elements within the current type element.

Since this stylesheet is used after the type value transformation, we may (and even must) take advantage of the results of that transformation, including noting that selected values on type-enum elements are marked with the value-is-set attribute.

The stylesheet source code can be found in examples/Common/VerySimple/Resources/structure_comments.xsl.

Limitations and Enhancements

Whilst the above modifications adds a comment field for each item with a type of "Personal", and whilst the comment field will appear and disappear for items as their type changes, such updates only take place when items and subitems are added and removed. We could add an update button to the page which performs an explicit refresh of the page without adding or removing anything, and for the sake of usability, we probably should add such a button (just below the Add item button):

<p>
<input name="update" type="submit" value="Update" />
</p>

However, we could also add an in-page update to make each comments field appear and disappear as soon as we have changed the type of an item.

Template Changes

We must first define a region of the template where a comment fields can be added and removed, regardless of whether such a field existed there before. The above template code needs modifying slightly to permit this:

  <p template:element="options" template:id="comment-node" id="{template:this-element()}">
<span template:element="comment">Comment:
<textarea template:attribute="value" name="{template:this-attribute()}" cols="40" rows="3">
<span template:value="$this-value" template:effect="replace">Some comment</span>
</textarea>
</span>
</p>

Here, we have added this region definition to the paragraph surrounding the comment field, annotating the paragraph with the following attributes:

Another change has been to put the template:element annotation inside the above fragment or region annotations. Had we not done this, the lack of a comment element in the form data could have prevented the id attribute from appearing in the Web page, this preventing any hope of an in-page update since there would be no way of knowing where such an update should be applied.

Adding JavaScript

Since we rely on JavaScript support in the browser, the following references to scripts must also be added to the template, as shown in the following excerpt:

<head>
<title>Example</title>
<script type="text/javascript" src="scripts/sarissa.js"> </script>
<script type="text/javascript" src="scripts/XSLForms.js"> </script>
</head>

These special script files can be found in examples/Common/VerySimple/Resources/scripts.

Now we can concentrate on adding the event which triggers an in-page update. Since it is the type values that cause each comment field to be added or removed, we add an event attribute on the form field responsible for displaying the type values:

  <p>
Item type:
<select template:element="type" name="{template:list-attribute('type-enum', 'value')}" multiple="multiple"
onchange="requestUpdate('{$application-url}comments', '{template:list-attribute('type-enum', 'value')}',
 '{template:other-elements(../options)}',
'{template:child-attribute('value', template:child-element('comment', 1, template:other-elements(../options)))}',
'/structure/item/options')"
>
<option template:element="type-enum" template:expr="@value-is-set" template:expr-attr="selected"
template:value="@value" value="{@value}" />
</select>
</p>

This complicated string calls a special update request JavaScript function which triggers the in-page update, and it specifies the following things:

  1. The URL which will serve the in-page update requested by this field. We use a special variable called application-url which will need to be provided to the template when generating the Web page.
  2. The fields which are going to be used in the processing of the update. Since the presence of the comment field depends on a specific type element and its type-enum elements' value attributes, we specify the names of the fields which yield these values.
  3. The region which is to be updated. Here, we recall that we defined the region using a special reference to the options element holding comment element. Thus, we use a special value which also refers to that element from the context of the type element.
  4. Even when the types are changed, it may be the case that an exposed comment field does not disappear (for example, if we already have "Personal" selected but select "Important" in addition), and so we need to provide the details of the field which holds the value of the comment text. We find such details by referencing the options element from the type element and stating that we want the value attribute on any comment element that may exist. Note that we cannot reference the comment element directly since it may not exist at first, but then come into being after an update, but not be referenced here in this parameter; therefore, we need to make up the final part of the reference using the special template:child-attribute and template:child-element functions.
  5. Finally, we need to provide some context to the application to tell it something about where in the complete form data structure the updated information resides.