XForms/Graph Viewer
Motivation
editSometimes you need to have a form edit data structures that are recursive. In other words: a structure that references itself.
This example has nodes that have links. These links in turn point back to nodes. This is an example of a self-referential data structure. This is common when manipulating graphs. Workflow systems frequently use these structures.
Link to Working Example
editXML Schema
editHere is a sample XML Schema with a recursive data type (a data type that includes an instance of itself):
graph.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="Graph">
<xs:annotation>
<xs:documentation>Comment describing your root element</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="graph-name">
<xs:annotation>
<xs:documentation>The name of the graph</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="graph-description" minOccurs="0"/>
<xs:element name="node" type="nodeType" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>A node that has zero or more links</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="nodeType">
<xs:annotation>
<xs:documentation>A node in a graph with 0 to n links</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="node-id">
<xs:annotation>
<xs:documentation>node identifier</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="links" type="linkType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="linkType">
<xs:annotation>
<xs:documentation>A link from one node to another.</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="link-id">
<xs:annotation>
<xs:documentation>link identifier</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="node" type="nodeType" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Instance Document
edit<Graph>
<graph-name>USA</graph-name>
<graph-description>Example of graph with states, counties, cities and neighborhoods.</graph-description>
<node>
<node-id>usa</node-id>
<links>
<link-id>states-in-the-us</link-id>
<node>
<node-id>california</node-id>
</node>
<node>
<node-id>kansas</node-id>
</node>
<node>
<node-id>illinois</node-id>
</node>
<node>
<node-id>iowa</node-id>
</node>
<node>
<node-id>minnesota</node-id>
<links>
<link-id>counties-in-minnesota</link-id>
<node>
<node-id>carver</node-id>
</node>
<node>
<node-id>dakota</node-id>
</node>
<node>
<node-id>hennepin</node-id>
<links>
<link-id>cities in hennpin county</link-id>
<node>
<node-id>bloomington</node-id>
</node>
<node>
<node-id>minneapolis</node-id>
</node>
<node>
<node-id>richfield</node-id>
</node>
<node>
<node-id>st-louis-park</node-id>
<links>
<link-id>neighborhoods-in-st-louis-park</link-id>
<node>
<node-id>aquila</node-id>
</node>
<node>
<node-id>cobblecrest</node-id>
</node>
<node>
<node-id>elmwood</node-id>
</node>
</links>
<!-- neighborhoods-in-st-louis-park -->
</node>
</links>
<!-- cities-in-hennepin county -->
</node>
<node>
<node-id>ramsey</node-id>
<links>
<link-id>cities in ramsey county</link-id>
<node>
<node-id>eagan</node-id>
</node>
<node>
<node-id>st-paul</node-id>
</node>
</links>
</node>
</links>
<!-- counties-in-minnesota -->
</node>
<node>
<node-id>new-york</node-id>
</node>
</links>
<!-- states-in-the-us -->
</node>
</Graph>
XForms Example
editThis example allows you to view a graph of geographic regions.
Make sure to copy and past from the edit view of cookbook since the greater than symbols get converted.
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<head>
<title>XForms Graph Viewer</title>
<style type="text/css">
body { font-family: Verdana, Helvetica, sans-serif }
output {font-size: 10pt}
.bold {font-weight: bold}
.indent {margin-left: 250px}
</style>
<xf:model>
<xf:instance xmlns="" id="saved-data" src="usa.xml" />
<xf:instance xmlns="" id="path">
<data>
<!-- set initial value to be the root node -->
<current-node-id>usa</current-node-id>
</data>
</xf:instance>
<xf:bind id="current-node-id" nodeset="instance('path')/current-node-id" />
<xf:submission id="save-to-file" method="put" action="usa.xml" replace="instance" instance="saved-data" />
</xf:model>
</head>
<body>
<h1>XForms Graph Viewer</h1>
<p>Example of viewing arbitrarily deep hierarchies with XForms v0.1</p>
<xf:input ref="graph-name">
<xf:label>Graph Name: </xf:label>
</xf:input>
<br />
<xf:textarea ref="graph-description">
<xf:label>Description: </xf:label>
</xf:textarea>
<br />
<br />
<xf:input bind="current-node-id" incremental="true">
<xf:label>Search for node id:</xf:label>
</xf:input>
<br />
<div class="bread-crumbs">
<p>Sample short-cuts:</p>
<xf:trigger>
<xf:label>usa</xf:label>
<xf:action ev:event="DOMActivate">
<xf:setvalue bind="current-node-id" value="'usa'" />
</xf:action>
</xf:trigger>
->
<xf:trigger>
<xf:label>minnesota</xf:label>
<xf:action ev:event="DOMActivate">
<xf:setvalue bind="current-node-id" value="'minnesota'" />
</xf:action>
</xf:trigger>
->
<xf:trigger>
<xf:label>hennepin</xf:label>
<xf:action ev:event="DOMActivate">
<xf:setvalue bind="current-node-id" value="'hennepin'" />
</xf:action>
</xf:trigger>
->
<xf:trigger>
<xf:label>st-louis park</xf:label>
<xf:action ev:event="DOMActivate">
<xf:setvalue bind="current-node-id" value="'st-louis-park'" />
</xf:action>
</xf:trigger>
</div>
<h3>Current Node</h3>
<xf:output ref="instance('path')/current-node-id" class="bold">
<xf:label>Current node id: </xf:label>
</xf:output>
<br/>
<xf:output ref="//node[node-id=instance('path')/current-node-id]/links/link-id" class="bold">
<xf:label>Link type: </xf:label>
</xf:output>
<!-- This is the critical line. Find all the nodes in the graph that have a node-id of the current-node-id -->
<xf:repeat nodeset="//node[node-id=instance('path')/current-node-id]/links/node" id="repeat-node1">
<xf:trigger class="indent">
<xf:label> <!-- the label on the button -->
<xf:output ref="node-id" />
</xf:label>
<xf:action ev:event="DOMActivate">
<xf:setvalue ref="instance('path')/current-node-id" value="instance('saved-data')//node[node-id=instance('path')/current-node-id]/links/node[position()=index('repeat-node1')]/node-id" />
</xf:action>
</xf:trigger>
</xf:repeat>
<h3>All Nodes:</h3>
<xf:repeat nodeset="//node" id="repeat-node2">
<xf:output ref="node-id">
<xf:label>Node Name: </xf:label>
</xf:output>
</xf:repeat>
<h3>Link Types:</h3>
<xf:repeat nodeset="//links" id="repeat-link">
<xf:output ref="link-id">
<xf:label>Link Name: </xf:label>
</xf:output>
</xf:repeat>
<xf:submit submission="save-to-file">
<xf:label>Save graph</xf:label>
</xf:submit>
</body>
</html>
Discussion
editMost of this is straight-forward XForms code. But there is one very tricky line. This is when you click on a node listed in the list of links and you want to make the node you just selected the current node.
The trick is is to use the position() to match the index() value. When the position matches the index you can set the current-node-id equal to this node.
<xf:setvalue ref="instance('path')/current-node-id" value="instance('saved-data')//node[node-id=instance('path')/current-node-id]/links/node[position()=index('repeat-node1')]/node-id" />
Thanks to Fraser for the tip!