XForms Tutorial: A Generic Application

The author
Steven Pemberton
, CWI, Amsterdam

Contents

Getting started

Go to cwi.nl/~steven/xforms/generic/

Set up the server for the exercises

What you will learn

This is a new tutorial, so I am happy to get feedback.

It assumes you know XForms to a certain level already.

There may be more material than we can cover in the time available, but the tutorial is online, so you can continue studying at home.

An Application

While we're going to build a generic application, we're going to do that by first making a specific application that we will steadily make more generic.

So to start, we will make a simple quick reference application for XForms.

The Data

The data we are using is a series of entries for aspects of XForms: elements, attributes, actions, functions. This is stored in a file called data.xml. Here is a taste:

<xforms>
   ...
   <entry class="element">
      <name>input</name>
      <common>Control Common</common>
      <common>inputmode</common>
      <common>incremental</common>
      <content><element>UICommon</element>*</content>
   </entry>
   <entry class="element">
      <name>output</name>
      <common>Control Common</common>
      <common>value</common>
      <common>mediatype</common>
      <content>PCDATA | 
               (<element class="deprecated">mediatype¹</element>, <element>UICommon</element>*)
      </content>
   </entry>
   ...
   <entry class="element">
      <name>submit</name>
      <common>Control Common</common>
      <attribute>
         <name>submission</name>
         <type>IDREF</type>
      </attribute>
      <content><element>UICommon</element>*</content>
   </entry>
   ...

Displaying the Data

Our first task is to display the data, which we will do in fairly generic manner. We have a top-level series of elements, each of which contains another series of elements. As a first try, we will do this:

<instance id="data" resource="data.xml"/>
 ...
<group>
   <label><output value="local-name(/*)"/><label>
   <repeat ref="entry">
      <repeat ref="@*">
         @<output value="local-name(.)"/>: <output ref="."/>    
      </repeat>
      <repeat ref="*">
         <output value="local-name(.)"/>: <output ref="."/>
      </repeat>
   </repeat>

What you can see is that we repeat over the top-level entry elements, and then within each entry, we repeat over the attributes and then the sub-elements, whatever they are. To see what they are, we display the name of the sub-element (with the local-name function) along with its value.

Displaying the Data

It looks like this:

Source

Displaying the Data

A repeat by default displays its content on a new line (actually CSS controls this and each repeat item has display: block). We can chnage that by adding CSS to make repeat items have display: inline. The repeats themselves still have display: block:

<style type="text/css">
   .xforms-repeat-item {display: inline}
</style>

Displaying the Data

This makes it a little easier to discern the structure:

Source

Displaying the Data

We add a group round the inner repeats with a class attribute, and some extra CSS, such as a border:

<style type="text/css">
   .xforms-repeat-item {display: inline}
   .entry {border-bottom: thin black solid}
</style>
 ...
<repeat ref="entry">
   <group class="entry">
      <repeat ref="@*">
         @<output value="local-name(.)"/>: <output ref="."/>    
      </repeat>
      <repeat ref="*">
         <output value="local-name(.)"/>: 
         <output ref="."/>
      </repeat>
   </group>
</repeat>

Displaying the Data

which now looks like this:

Source

Displaying the Data

Now we have a better idea of the structure of the data.

Instead of displaying the name of the sub-element, we will use that name as the CSS class for displaying the element's content, with CSS rules for each type of element:

<repeat ref="entry">
   <group class="entry">
      <repeat ref="*">
         <output class="{local-name(.)}" ref="."/>
      </repeat>
   </group>
</repeat>

We can now add CSS rules using the names of the sub-elements, like:

.name {font-weight: bold; display: inline-block; width: 14ex}
 .common {color: green}
 .common::after {content: ", "}
 .attribute {color: blue}
 .attribute::after {content: ", "}
 .parameter {color: blue; padding-right: 1ex}
 .content {color: red}

Displaying the Data

Source

Exercise

Move the CSS to a separate file.
Hint: In the head use <link href="..." rel="stylesheet" type="text/css"/>
Hint 2: You need to add at the top of the file after the xml-stylesheet line, the line

<?css-conversion no?>

To get a copy of the example XForm, right click on the 'Source' link above, and choose "Save link as...".

Save it to the directory called xforms where you unpacked the server.

Run it, use the link http://localhost:8082/xforms/display.xhtml

(If you download examples from later chapters, you have to download both the xform xyz.xhtml and its matching css file xyz.css.)

Search

We're now going to add a search field, and only display the entries that match.

For that we need a new instance for the search string:

<instance id="q">
   <q xmlns="">
      <q/>
   </q>
</instance>

and an input control for the search string:

<input incremental="true" ref="instance('q')/q">
   <label>Search</label>
</input>

and change the display so that it only displays entries that match:

<repeat ref="entry[contains(lower-case(.), lower-case(instance('q')/q))]">

Search

This already gives a very usable application:

Source

Exercise

After the search box add a trigger to clear the search box.

Viewing a Single Entry

Now we're going to add the ability to look at a single entry in detail.

Uses a switch element, with one case for viewing all entries, and one for viewing just one (more possibilities later):

<switch>
   <case id="viewall">
      ... the stuff we already have ...
   </case>
   <case id="viewone">
      ... the new stuff ...
   </case>
</switch>

To switch between the two we add a trigger in front of each displayed entry in the View All case, that will cause just that entry to be viewed, and a trigger in the View One case that will switch back to the View All. The second of these is the easier:

<trigger>
   <label>View all</label>
   <toggle case="viewall" ev:event="DOMActivate"/>
</trigger>

Viewing a Single Entry

For the other case, we need to record which entry we want to view before toggling:

<repeat ref="entry[contains(lower-case(.), lower-case(instance('q')/q))]">
  <group class="entry {@class}">
    <trigger>
      <label>Show</label>
      <action ev:event="DOMActivate">
        <setvalue ref="instance('q')/selected"
                  value="count(context()/preceding-sibling::*)+1"/>
        <toggle case="viewone"/>
      </action>
    </trigger>
    ...

Clearly this has required the addition of a new value selected in the q instance.

Viewing a Single Entry

The case for viewing one entry can then display the selected entry:

<group ref="instance('data')/*[position()=instance('q')/selected]">
   <output ref="@class"/>
   <repeat ref="*">
      <group>
         <output value="local-name(.)"/>: 
         <output ref="."/>
      </group>
   </repeat>
</group>

Viewing a Single Entry

Source

Exercise

Editing an Entry

Now to add the ability to edit an entry.

Add a new case to the switch:

<switch>
   <case id="viewall">
     ...
   </case>
   <case id="viewone">
      ...
   </case>
   <case id="edit">
      ...
   </case>
</switch>

Instead of displaying the selected entry, we copy it somewhere, allow the copy to be edited, and then the edited version can either be accepted, and thus copied back, or cancelled, in which case nothing will have changed.

Editing an Entry

We'll start this action from the view-one case. Remember, that the value of selected has already been set:

<trigger>
   <label>edit</label>
   <action ev:event="DOMActivate">
       <insert ref="instance('copy')"
               origin="instance('data')/*[
                          position()=instance('q')/selected]"/>
       <toggle case="edit"/>
   </action>
</trigger>

For this we need a new instance to store the copy in:

<instance id="copy">
    <entry xmlns=""/>
</instance>

and now we can write the case for editing.

Editing an Entry

This is similar to the view-one case, except using input controls instead of output:

<case id="edit">
    <label>Edit</label>
    <group ref="instance('copy')">
       <input ref="@class"><label>Class</label></input>
       <repeat ref="*">
          <group>
             <input ref=".">
                <label><output value="local-name(.)"/></label>
             </input>
          </group>
       </repeat>

Editing an Entry

Of course, just changing the content of elements is not enough: you might want to delete or insert new ones. This is fairly easy. After the input control add:

<trigger>
   <label>+</label>
   <hint>Add another</hint>
   <insert ref="." ev:event="DOMActivate"/>
</trigger>
<trigger>
   <label>&#128465;</label>
   <hint>Delete</hint>
   <delete ref="." ev:event="DOMActivate"/>
</trigger>

Editing an Entry

We then follow with the cancel and save triggers. Cancel just returns us to the view-one case, leaving the data untouched:

<trigger>
   <label>Cancel</label>
   <toggle case="viewone" ev:event="DOMActivate"/>
</trigger>

The save trigger inserts the edited entry after the original entry, and deletes the original:

<trigger>
   <label>Save</label>
   <action ev:event="DOMActivate">
      <insert position="after"
              ref="instance('data')/*[position()=instance('q')/selected]"
              origin="instance('copy')/entry"
      />
      <delete ref="instance('data')/*[position()=instance('q')/selected]"/>
      <toggle case="viewone"/>
   </action>
</trigger>

Editing an Entry

Giving:

Source

Exercise

An entry has one structured sub-element: <attribute> that has children <name> and <type>. That's not a problem when viewing, since outputting a structured element just concatenates the text nodes of the children. But for editing, we need to access the individual fields. This exercise is to fix that.

While an easy solution would be to treat attribute specially, we want a generic solution. For output, that would look like this. Instead of the current:

<repeat ref="*">
   <group>
      <output value="local-name(.)"/>: <output ref="."/>
   </group>
</repeat>

we use:

<repeat ref="*">
   <group>
      <output value="local-name(.)"/>:
         <output ref=".[count(*) = 0]"/>
         <repeat ref="*">
            <output class="child" value="local-name(.)"/>: <output ref="."/>
         </repeat>
   </group>
</repeat>

The second output line only outputs the current element's content if it has no children. The repeat after that only does anything if there are children.

Your task is to change the edit case similarly.

(Note that [not(*)] is an equivalent option for [count(*) = 0].)

Saving the Data

Having edited the data, we should ensure that it gets saved.

For this we need a submission that saves the data back to the file:

<submission id="save" ref="instance('data')" resource="data.xml"
            method="put" validate="false" replace="none"/>

and activate it when we change the data, by adding a <send/> to the save trigger:

<trigger>
   <label>Save</label>
   <action ev:event="DOMActivate">
      <insert position="after"
              ref="instance('data')/*[position()=instance('q')/selected]"
              origin="instance('copy')/entry"
      />
      <delete ref="instance('data')/*[position()=instance('q')/selected]"/>
      <send submission="save"/>
      <toggle case="viewone"/>
   </action>
</trigger>

Saving the Data

You should always check that a submission gets successfully done:

<submission id="save" ref="instance('data')" resource="data.xml"
            method="put" validate="false" replace="none">
   <action ev:event="xforms-submit-error">
      <setvalue ref="instance('q')/message"
                value="concat('Not saved: ', event('response-reason-phrase'))"/>
      <message>Submission error on save
               error-type: <output value="event('error-type')"/>
               error-message: <output value="event('error-message')"/>
               response-status-code: <output value="event('response-status-code')"/>
               response-reason-phrase: <output value="event('response-reason-phrase')"/>
               resource-uri: <output value="event('resource-uri')"/>
      </message>
   </action>
   <action ev:event="xforms-submit-done">
      <setvalue ref="instance('q')/message"/>
   </action>
</submission>

Saving the Data

This requires a new value to be added to the q instance:

<instance id="q">
   <q xmlns="">
      <q/>
      <selected/>
      <message/>
   </q>
</instance>
<bind ref="instance('q')/message" relevant=". != ''"/>

which we should also display somewhere:

<output ref="instance('q')/message"><label>Error</label></output>

Since it is only relevant when it is non-empty, it will only be displayed when it has a value.

Saving the Data

Source

Exercise

Give the error message a red background.

Hint: put a value in the message element during testing.

Backing up the data

Since we're now overwriting the data file, it would be good practice to create a backup at startup in case of disaster:

<send submission="backup" ev:event="xforms-ready"/>

with a similar submission to the earlier one, though without a message since the user can't do anything about it if the backup fails:

<submission id="backup" ref="instance('data')" resource="backup.xml"
            method="put" validate="false" replace="none">
   <action ev:event="xforms-submit-error">
      <setvalue ref="instance('q')/message"
                value="concat('Backup failed: ', event('response-reason-phrase'))"/>
   </action>
   <action ev:event="xforms-submit-done">
      <setvalue ref="instance('q')/message"/>
   </action>
</submission>

Backing up the data

Source

Exercise

You only really need a backup if the data gets changed: if you are only browsing the data, a backup is not needed.

Make it so that the backup only gets done the first time a change is done, just before the insert and delete. Record that the backup has been done and don't backup on subsequent saves.

Restoring the Data

Since we are backing up the data, we can easily add a trigger to restore the data to its state at the start:

<trigger>
   <label>Restore</label>
   <hint>Restore the data to its state at the beginning of the run</hint>
   <send submission="restore" ev:event="DOMActivate/>
</trigger>

which needs a new submission similar to the others:

<submission id="restore" resource="backup.xml" serialization="none" 
            method="get" replace="instance" instance="data">
   <action ev:event="xforms-submit-error">
      <setvalue ref="instance('q')/message"
                value="concat('Restore failed: ', event('response-reason-phrase'))"/>
      <message>Submission error on restore...</message>
   </action>
   <action ev:event="xforms-submit-done">
      <setvalue ref="instance('q')/message"/>
   </action>
</submission>

Restoring the Data

We shouldn't offer the restore trigger until a backup has been successful.

So we record the state of the backup. We add a new value to the q instance:

<backup/>

If it is empty, there is no backup to restore, otherwise there is.

We should only display the restore trigger when there is a backup:

<bind ref="instance('q')/backup" relevant=". != ''"/>

with:

<trigger ref="instance('q')/backup">
   <label>Restore</label>
   <hint>Restore the data to its state at the beginning of the run</hint>
   <send submission="restore" ev:event="DOMActivate/>
</trigger>

and then set the value at suitable places.

Restoring the Data

Firstly, when we do a backup:

<submission id="backup" ref="instance('data')" resource="backup.xml"
            method="put" validate="false" replace="none">
   <action ev:event="xforms-submit-error">
      <setvalue ref="instance('q')/message"
                value="concat('Backup failed: ', event('response-reason-phrase'))"/>
      <setvalue ref="instance('q')/backup"/>
   </action>
   <action ev:event="xforms-submit-done">
      <setvalue ref="instance('q')/message"/>
      <setvalue ref="instance('q')/backup">done</setvalue>
   </action>
</submission>

Restoring the Data

Secondly when we do a restore, since now the data and backup are identical, so a restore has no purpose until the next change:

<submission id="restore" resource="backup.xml" serialization="none" 
            method="get" replace="instance" instance="data">
   ...
   <action ev:event="xforms-submit-done">
      <setvalue ref="instance('q')/message"/>
      <setvalue ref="instance('q')/backup"/>
   </action>
</submission>

Restoring the Data

Source

Exercise

Every time we make a change to the data, it gets saved so that the internal data and the file are always the same.

That means that after a successful restore we must also do a save, otherwise the file still has the old changed data.

Add this.

Adding an Entry

Adding an entry is similar to editing an existing entry, so we merge the two cases. The main differences are that the source of the value being edited comes from somewhere else, and there will be no existing entry selected.

We'll need a blank template to provide the source of the value to be edited, and we can use a trigger to add it:

<trigger>
   <label>+</label>
   <hint>Add an entry</hint>
   <action ev:event="DOMActivate">
      <setvalue ref="instance('q')/selected"/>
      <insert ref="instance('copy')"
              context="instance('copy')"
              origin="instance('template')/*"/>
      <toggle case="edit"/>
   </action>
</trigger>

(Note that instance('template')/* is just the root element of the template instance, whatever that root is called.)

Adding an Entry

To create a template of what an empty entry looks like, we now have to examine the data in a little more detail.

Each entry has a class attribute which represented what sort of item is being described. The class can be: model, subelement, control, common, collection, element, function, action, or deprecated.

Each entry has a name sub-element.

Depending on the value of class, the entry can have other different subelements.

Adding an Entry

Here is an instance for this, containing a template entry with the possible elements listed above:

<instance id="template">
   <template xmlns="">
      <entry class="">
         <name/>
         <common/>
         <attribute>
            <name/>
            <type/>
         </attribute>
         <content/>
         <type/>
         <parameter/>
         <description/>
      </entry>
   </template>
</instance>

Adding an Entry

We can now specify when these different elements are relevant:

<bind ref="@class" required="true()"/>
<bind ref="common" relevant="../@class!='function' and ../@class!='collection'"/>
<bind ref="attribute" relevant="../@class!='function' and ../@class!='collection'"/>
<bind ref="content" relevant="../@class!='function' and ../@class!='common'"/>
<bind ref="type" relevant="../@class='function'"/>
<bind ref="parameter" relevant="../@class='function'"/>
<bind ref="description" relevant="../@class='function'"/>

These apply to the copy instance that we edit.

Adding an Entry

Here are the possible class values:

<instance id="classes">
   <classes xmlns="">
      <class>model</class>
      <class>subelement</class>
      <class>control</class>
      <class>common</class>
      <class>collection</class>
      <class>element</class>
      <class>function</class>
      <class>action</class>
      <class>deprecated</class>
   </classes>
</instance>

Adding an Entry

There are two changes we have to make to the edit case. One is that we want to be able to change the class attribute properly. Change

<input ref="@class"><label>Class</label></input>

to

<select1 ref="@class">
   <label>Class</label>
   <itemset ref="instance('classes')/class">
      <label ref="."/>
      <value ref="."/>
   </itemset>
</select1>

Adding an Entry

When we save the changes, we need to distinguish the two cases of editing and adding:

<action ev:event="DOMActivate">
   <send if="instance('q')/backup = ''" submission="backup"/>
   <action if="instance('q')/selected != ''">
      <insert position="after"
              ref="instance('data')/*[position()=instance('q')/selected]"
              origin="instance('copy')"
      />
      <delete ref="instance('data')/*[position()=instance('q')/selected]"/>
   </action>
   <action if="instance('q')/selected = ''">
      <insert position="after"
              ref="instance('data')/*"
              origin="instance('copy')"
      />
   </action>
   <send submission="save"/>
   <toggle case="viewall"/>
</action>

Adding an Entry

Source

Exercise

After doing the save, we return to the 'view all' view.

However, we started the edit case from 'view one', and the add case from 'view all', so really we should return to where we came from.

Change it so that saving an edit returns to the view one case, and saving an add returns to the view all case.

Deleting an Entry

Deleting an entry is just a trigger that deletes the selected entry:

<trigger>
   <label>Delete</label>
   <action ev:event="DOMActivate">
      <send if="instance('q')/backup = ''" submission="backup"/>
      <delete ref="instance('data')/*[position()=instance('q')/selected]"/>
      <send submission="save"/>
      <toggle case="viewall"/>
   </action>
</trigger>

Deleting an Entry

To protect the user from accidently deleting, make it a two step process. Provide a trigger that causes a second trigger to appear that really does the delete:

<trigger appearance="minimal">
   <label>Delete> </label>
   <action ev:event="DOMActivate">
      <setvalue ref="instance('q')/really" value="if(.='', 'yes', '')"/>
   </action>
</trigger>
<trigger ref="instance('q')/really">
   <label>Really delete</label>
   <action ev:event="DOMActivate">
      <send if="instance('q')/backup = ''" submission="backup"/>
      <delete ref="instance('data')/*[position()=instance('q')/selected]"/>
      <send submission="save"/>
      <setvalue ref="instance('q')/really"/>
      <toggle case="viewall"/>
   </action>
</trigger>

Deleting an Entry

This uses a new value really, that is only relevant if it has non-empty:

<bind ref="instance('q')/really" relevant=". != ''"/>

Source

Deleting an Entry

We can put the delete trigger in the view one case.

Source

Exercise

You may have noticed that when "Really delete" pops up, that clicking on the Delete trigger cancels it.

Modify it so that the label on the Delete trigger changes to "Cancel" when the "Really delete" trigger is visible.

Hint: Use an output in the label that depends on the value of the really location.

Checking Before Saving

Sometimes I have an application open on my desktop at home, and then when I'm out, I use it on my phone. When I return home, I start using it on my desktop again, but the data is now out-of-date with the changes I made on my phone.

So a check is needed whenever the data is about to change, to ensure the data and what has been saved in the file are the same.

We do this by recording the last-modified time of the file every time it is saved. Then whenever the data is about to be changed, we check that the last-modified time hasn't changed.

Checking Before Saving

Whenever you submit to a server, along with any data returned, there is also some metadata. We've seen that already with the error messages for the xforms-submit-done or the xforms-submit-error event, where we use the the event() function:

<message>Submission error on save
         error-type: <output value="event('error-type')"/>
         error-message: <output value="event('error-message')"/>
         response-status-code: <output value="event('response-status-code')"/>
         response-reason-phrase: <output value="event('response-reason-phrase')"/>
         resource-uri: <output value="event('resource-uri')"/>
</message>

Checking Before Saving

To summarise the values. For both events:

Additionally for xforms-submit-error:

Checking Before Saving

To see it in action, here is an XForm that gets a non-existent file, or does a HEAD on the data.xml file:

Source

If you click on the HEAD button, you will see one of the headers is for last-modified, with a date and time value. That's the one we are interested in.

Checking Before Saving

The above XForm uses the fact that many events bubble: in this case the two events get dispatched to the submission elements, but then travel up the tree of elements.

This means that you can catch them higher up (as in this case) if you don't care which submission element they were dispatched to.

<action ev:event="xforms-submission-error">
   ...
</action>

<submission .../>

<submission .../>

This will catch submission errors from both submissions.

Checking Before Saving

What we are going to do is add a submission to do a HEAD on the data file, and store the value of last-modified for later use.

<submission id="head" resource="data.xml" serialization="none"
            replace="none" method="head">
   <action ev:event="xforms-submit-done">
      <setvalue ref="instance('q')/last-modified"
          value="event('response-headers')//[name='last-modified']/value"/>
   </action>
   <action ev:event="xforms-submit-error">
      <message>...</message>
   </action>
</submission>

When we initially load data.xml, and every time we save to it, we will activate that submission:

<action ev:event="xforms-ready">
   <send submission="head"/>
</action>

Checking Before Saving

Just before changing the data we determine if the value is still the same, and warn if it isn't:

<submission id="check" resource="data.xml" serialization="none"
            replace="none" method="head">
   <action ev:event="xforms-submit-done">
      <setvalue ref="instance('q')/check" 
                value="event('response-headers')//[name='last-modified']/value"/>
      <message if="instance('q')/check != instance('q')/last-modified">
         The data has been modified outside of this app.
         You should reload the data first.
      </message>
   </action>
   <action ev:event="xforms-submit-error">
      <message>...</message>
   </action>
</submission>

Checking Before Saving

Add a submission to reload the data, like restore, but from a different file:

<submission id="reload" resource="data.xml" serialization="none" 
            replace="instance" instance="data" method="get">
   <action ev:event="xforms-submit-done">
      <setvalue ref="instance('q')/last-modified" 
                value="event('response-headers')//[name='last-modified']/value"/>
   </action>
   <action ev:event="xforms-submit-error">
      <message>Submission error on RELOAD...</message>
   </action>
</submission>

and a trigger to activate it, using the same method of making it visible when needed:

<bind ref="instance('q')/check"
      relevant=". != '' and . != ../last-modified"/>
 ...
<trigger ref="instance('q')/check">
   <label>Reload<label>
   ...

Exercise

All the submission elements have a similar structure in the xforms-submit-error sections.

We can use the fact that xforms-submit-error bubbles to handle it twice: once for the specific part in the body of the submission, and once above, to issue the error message.

Do this.

Making it More Generic

Our aim is that as little of the actual code needs to be changed when using it to create a different application.

First: move all application-specific data to external files:

<instance id="template" resource="template.xml"/>
<instance id="classes" resource="classes.xml"/>

Making it More Generic

The code in two places assumes that the repeating element in the data is called entry.

One of these is when we copy the template into the copy instance:

<insert ref="instance('copy')"
        context="instance('copy')"
        origin="instance('template')/entry"/>

Since this is just the root element of the template, we can replace it without any harm with:

<insert ref="instance('copy')"
        context="instance('copy')"
        origin="instance('template')/*"/>

Making it More Generic

The other place is in the repeat when viewing all:

<repeat ref="entry[contains(lower-case(.), lower-case(instance('q')/q))]">

While we could use the same trick here, it would prevent the data containing other elements than the repeated one. Instead we will create a new instance (in a file) that contains meta-information about the application.:

<meta>
   <title>XForms Quick Reference</title>
   <entry>entry</entry>
</meta>

Then we can display the title at the top:

<group xmlns="http://www.w3.org/2002/xforms">
      <label><output ref="instance('meta')/title"/></label>

And repeat over whatever the repeating entry is called:

<repeat ref="*[local-name()=instance('meta')/entry and
             contains(lower-case(.), lower-case(instance('q')/q))]">

Making it More Generic

We can also remove the dependency on the name of the data file. We can add that to the meta file:

<meta>
   <title>XForms Quick Reference (generic)</title>
   <data>data.xml</data>
   <entry>entry</entry>
</meta>

and then on all submissions that use it, replace resource="data.xml" with resource="{instance('meta')/data}". (You could do that with backup.xml as well if you wanted)

Making it More Generic

There is one place where you can't do that replacement, namely on the instance:

<instance id="data" resource="data.xml"/>

To replace that we provide an empty instance:

<instance id="data">
   <dummy xmlns=""/>
</instance>

and ensure that the data file gets loaded at startup (luckily easy, because we already have the mechanism for doing that):

<action ev:event="xforms-ready">
   <send submission="reload"/>
</action>

Originally there was a <send submission="head"/> done at startup, but we don't need that any more, because reload does that anyway.

Exercise

Do this.

A Different Application

In the file claims.xml there is a set of entries about travel costs of three types: travel that has been done, but not yet claimed; entries that have been claimed, and not yet received; claims that have been made and received.

Adapt the application to instead deal with this data.

Hint: First modify the meta, classes, template, and style.css files.

The data is in date order. Ideally you would like the display to be ordered on the classes: first the unclaimed, then the claimed, then the received, which you could do like this:

<repeat ref="instance('classes')/class">
   <repeat ref="instance('data')/*[name()=instance('meta')/entry and 
                                   @class=context() and 
                                   contains(lower-case(.), instance('q')/lcsearch)]">

Exercise

Do it

The End