In the previous tutorial we began building an XForms to-do list. The last exercise left us with a to-do list that allowed us to view an edit a single task, not terribly useful, but a starting point. In this tutorial we are going to build on the first and allow a user to manage multiple tasks.

A screenshot of the complete part 1 of the todo list

Our todo list where we left off.

 Extending our data model

As with the first tutorial, the first thing we need to do is work with the data model, altering it to allow for the management of multiple tasks. To do this we will change our XForms data instance to capture a task list which itself contains a number of tasks.

1
2
3
4
5
6
<xf:instance xmlns="">
    <tasklist>
        <task complete="false" dueDate="2013-07-27">Pick up milk</task>
        <task complete="true" dueDate="2013-07-27">Make tutorial part 1</task>
    </tasklist>
</xf:instance>

Unfortunately, the XForms will continue to look the same as the inputs that were placed in HTML the first tutorial expect a single task.

Looping through the data model

Having defined our repeating data instance, writing the declarations for a repeating user interface becomes a trivial affair – we simply need to wrap our current elements in an [In the previous tutorial we began building an XForms to-do list. The last exercise left us with a to-do list that allowed us to view an edit a single task, not terribly useful, but a starting point. In this tutorial we are going to build on the first and allow a user to manage multiple tasks.

A screenshot of the complete part 1 of the todo list

Our todo list where we left off.

 Extending our data model

As with the first tutorial, the first thing we need to do is work with the data model, altering it to allow for the management of multiple tasks. To do this we will change our XForms data instance to capture a task list which itself contains a number of tasks.

1
2
3
4
5
6
<xf:instance xmlns="">
    <tasklist>
        <task complete="false" dueDate="2013-07-27">Pick up milk</task>
        <task complete="true" dueDate="2013-07-27">Make tutorial part 1</task>
    </tasklist>
</xf:instance>

Unfortunately, the XForms will continue to look the same as the inputs that were placed in HTML the first tutorial expect a single task.

Looping through the data model

Having defined our repeating data instance, writing the declarations for a repeating user interface becomes a trivial affair – we simply need to wrap our current elements in an](http://www.w3.org/TR/xforms/#ui-repeat-module “XForms repeat module”) (sorry for the repetition (and that pun)). The nodeset or binding in a repeat element allows us to point to a set of elements that will have the child xforms controls under the repeat applied the them.

1
2
3
<xf:repeat nodeset="/tasklist/task">
        ...
</xf:repeat>

So in the above code we are declaring that we are repeating across the task elements under the tasklist node. In this case we have been very specific, declaring the XPath for the repeated element, but  the important thing to note, is that this would repeat any matching elements. So if we had multiple tasklists, we could target all tasks by using the Xpath “//task”.

Now that we have declared what elements to loop over, the next task is to change the inputs to be relative to the repeated element, so inside the body we get:

1
2
3
4
5
6
7
8
9
10
11
<xf:repeat nodeset="/tasklist/task">
    <xf:input ref=".">
        <xf:label>Task name:</xf:label>
    </xf:input>
    <xf:input ref="@complete">
        <xf:label>Complete:</xf:label>
    </xf:input>
    <xf:input ref="@dueDate" >
        <xf:label>Due date:</xf:label>
    </xf:input>
</xf:repeat>

And our form now looks like this:

A repeating form.

A repeating form.

Sadly, while we are getting the intended repetitions, it doesn’t look right.

Intertwining HTML and XForms

What we have here isn’t just a set of some unrelated fields, but could be argued to be tabular data. So we should mark it up as such. Fortunately, inside a repeat element everything is repeated, not just the XForms controls. So, by adding a table wrapper with appropriate headings, we can wrap the repeat like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<table>
    <thead>
        <tr>
            <th>Task name</th>
            <th>Complete</th>
            <th>Due date</th>
        </tr>
    </thead>
    <tbody>
        <xf:repeat nodeset="/tasklist/task">
            <tr>
               <!-- Each xf:input wrapped in its own td -->
            </tr>
        </xf:repeat>
    </tbody>
</table>

Since we now have labels for each field in the header, we can remove them and we get something that looks like this:

A tabular to-do list

A tabular to-do list

This could have also been done using only CSS (and I’ll leave the arguments about table layouts for the comments).

Reviwing our HTML+XForm so far

Now is a good point to show all of the code and look at the changes. So far this tutorial we’ve extended the data model, wrapped our inputs inside of a repeat, and we’ve remove the labels in lieu of table headers and remove the CSS as our inputs are now wrapped in table cells:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml-stylesheet href="xsltforms/xsltforms.xsl" type="text/xsl"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms" >
    <head>
        <title>XForms todo list</title>
        <xf:model>
            <xf:instance xmlns="">
                <tasklist>
                    <task complete="false" dueDate="2013-07-27">Pick up milk</task>
                    <task complete="true" dueDate="2013-07-27">Make tutorial part 1</task>
                </tasklist>
            </xf:instance>
        </xf:model>
    </head>
    <body>
        <table>
            <thead>
                <tr>
                    <th>Task name</th>
                    <th>Complete</th>
                    <th>Due date</th>
                </tr>
            </thead>
            <tbody>
                <xf:repeat nodeset="/tasklist/task">
                    <tr>
                        <td><xf:input ref="."/></td>
                        <td><xf:input ref="@complete"/></td>
                        <td><xf:input ref="@dueDate" /></td>
                    </tr>
                </xf:repeat>
            </tbody>
        </table>
    </body>
</html>

Adding and deleting tasks

To now we’ve only been able to manage tasks that already are in the data model, so the next step is to be able to add tasks of our own and delete old ones. To do this we need to add some buttons to the interface. In XForms the HTML button is replaced by an XForms trigger. So if we insert this code after the table:

1
2
3
<xf:trigger>
    <xf:label>Add Task</xf:label>
</xf:trigger>

Gives us this:

Todo list with a new button

Todo list with a new button

However, at this point the button still does nothing, we need to attach an action or multiple actions to perform for a specific XForms event for this trigger. Actions can be performed for a number of reasons, such as the XForm being refreshed, when data elements are deleted or, as in this case, when a button is triggered. So we can attach an action to the trigger like so:

1
2
3
4
5
6
<xf:trigger>
    <xf:label>Add Task</xf:label>
    <xf:action ev:event="DOMActivate">
        <xf:insert nodeset="/tasklist/task" at="last()" position="after" />
    </xf:action>
</xf:trigger>

In this example, we have an action that is fired when this button is “activated”, and this triggers a task to be inserted at the end of the tasklist after the last element. However, the way this works, it will duplicate the last element, so we can remove the data in the new element like this:

1
2
3
4
5
6
7
8
9
<xf:trigger>
    <xf:label>Add Task</xf:label>
    <xf:action ev:event="DOMActivate">
        <xf:insert nodeset="/tasklist/task" at="last()" position="after" />
        <xf:setvalue ref="/tasklist/task[last()]" value="" />
        <xf:setvalue ref="/tasklist/task[last()]/@complete" value="false()" />
        <xf:setvalue ref="/tasklist/task[last()]/@dueDate" value="'1970-01-01'" />
    </xf:action>
</xf:trigger>

Here we have set the value of the texts and clears or resets the form data of the new element using the XPath evaluation in the @value attribute – hence the double quotes and the false() function. This gives us this:

A task list with a new task!

A task list with a new task!

The last thing to do is add a trigger to delete the currently selected row. This is very much like the last:

1
2
3
4
5
<xf:trigger>
    <xf:label>Delete task <xf:output value="index('tasks')"/>
    </xf:label>
    <xf:delete ev:event="DOMActivate" nodeset="/tasklist/task" at="index('tasks')"/>
</xf:trigger>

The two differences are that we have use the ability to shortcut how events are attached to actions. Rather than use an action element, we can attach the event attribute directly to the specific action itself. Secondly, we’ve also added an output element so we can see which task we are about to delete based on the index of the repeat element (which we have now given the id “tasks” to refer to). We can also style the currently listed row using CSS:

1
2
3
.xforms-repeat-item-selected * {
    background-color: #DDF;
}

All up this gives us:

A nearly completed XForm to-do list

A nearly completed XForm to-do list

A nearly complete To-do list! The code is starting to grow, but you can check out the code on the XForms To-do List Github.

In the next tutorial we will look at how we can provide data validation that will both alter our data and interface, and edit the inputs to provide hints when invalid data is entered.