Using Bind with the Relevant Attribute

edit

XForms also allows you to conditionally display part of a form based on some value in your instance data. Typically this is used when the answer to one field of the form conditionally displays another part.

The format of this is to use the xf:bind statement within in the xf:model.

<xf:model>
   <xf:bind nodeset="NodeYowWantToConditionallyDisplay" relevant="XPathExpression"/>
</xf:model>

The following examples demonstrate this.

edit

Relevancy Demos Note: RelevancySelector.xhtml in the example application is incomplete.

Bind to a Decimal

edit

In the following example, the second input field is conditionally displayed based on the integer value of the first instance value.

<html
   xmlns="http://www.w3.org/1999/xhtml"
   xmlns:xf="http://www.w3.org/2002/xforms"
   xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <title>Testing the XForms bind relevant attribute.</title>
   <head>
   <xf:model>
      <xf:instance>
         <var xmlns="">
            <first>1</first>
            <second>This is the second value</second>
         </var>
      </xf:instance>
      <xf:bind nodeset="/var/first" type="xs:decimal" />
      <xf:bind nodeset="/var/second" relevant="/var/first &gt; 0" />
   </xf:model>
   </head>
   <body>
      <p>Demonstration of relevant fields.</p>
      <xf:select1 ref="/var/first" >
         <xf:label>Should I show the second question? </xf:label>
         <br />
         <xf:item select="yes">
            <xf:label>Yes Please!</xf:label>
            <xf:value>1</xf:value>
         </xf:item>
         <xf:item>
            <xf:label>No Thank You</xf:label>
            <xf:value>0</xf:value>
         </xf:item>
      </xf:select1>
      <br />
      <xf:input ref="/var/second">
         <xf:label>Second value: </xf:label>
      </xf:input>
   </body>
</html>

Using Multiple Predicates in Binds

edit

Sometimes you have a sequence of many items and the display of one item depends on the values of other items. Predicates are a way of appending AND/OR operations to the end of a path expression used as a relevancy expression.

The following example uses a bind with two predicates. The second item in a sequence is bound to the first item. To do this you must select the second item item[2] in the nodeset and add [. > 2.0] to end of the item[1] predicate.

<html
   xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:xf="http://www.w3.org/2002/xforms"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <head>
      <xf:model>
         <xf:instance id="instanceData">
            <data xmlns="">
               <item>0.00</item>
               <item>0.00</item>
            </data>
         </xf:instance>
         <!-- this rule will only allow the second item to be displayed if the first value is over 2.0 -->
         <xf:bind nodeset="instance('instanceData')/item[2]" relevant="instance('instanceData')/item[1][. &gt; 2.0]" />
      </xf:model>
   </head>
   <body>
      <xf:input ref="instance('instanceData')/item[1]">
         <xf:label>First item: </xf:label>
      </xf:input>
      <br />
      <xf:input ref="instance('instanceData')/item[2]">
         <xf:label>Second item: </xf:label>
      </xf:input>
   </body>
</html>

Bind to Boolean

edit

Since parts of a form are usually either visible or not visible, it is natural to use a boolean value to determine if the field should be displayed.

In this example if InputIndicator is true, the second output instance is visible.

If the InputIndicator is false, the second output is not visible.

Note that expression .='true' is used. This is a string comparison. Ideally you would just be able to test a node using mynoode=true() but I have had problems with this method.

<?xml version="1.0" encoding="UTF-8"?>
<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>Example of binding to to boolean controls</title>
   </head>
   <body>
      <xf:model id="model">
         <xf:instance id="input">
            <DataIn xmlns="">
               <InputIndicator>false</InputIndicator>
            </DataIn>
         </xf:instance>
         <!-- make the input data type be a XML Schema type boolean -->
         <xf:bind id="input_bind" nodeset="/DataIn/InputIndicator" type="xs:boolean"></xf:bind>
         <!-- second instance bound to outputs -->
         <xf:instance id="output">
            <DataOut xmlns="">
               <OutputValue>Hello World!</OutputValue>
            </DataOut>
         </xf:instance>
         <!-- if the input is true, then the output is relevent -->
         <xf:bind id="output_bind" nodeset="instance('output')/OutputValue" relevant="instance('input')/InputIndicator[.='true']"/>
      </xf:model>
      <p>
         <xf:input bind="input_bind">
            <xf:label>Check to see the value of output: </xf:label>
         </xf:input>
     <br/>
         <xf:output bind="output_bind">
            <xf:label>Value of Output: </xf:label>
         </xf:output>
      </p>
   </body>
</html>

In this example, a simple checkbox is used to conditionally display a field.

<html
   xmlns="http://www.w3.org/1999/xhtml"
   xmlns:xf="http://www.w3.org/2002/xforms"
   xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <title>Testing the relevant attribute of the bind element with boolean values.</title>
   <xf:model>
      <xf:instance>
         <var xmlns="">
            <first>true</first>
            <second/>
         </var>
      </xf:instance>
      <xf:bind nodeset="/var/first" type="xs:boolean" />
      <xf:bind nodeset="/var/second" relevant="/var/first='true'" />
   </xf:model>
   <body>
      <p>The input field should only display if the first value is checked.</p>
      <xf:input ref="/var/first">
         <xf:label>Display the next input?: </xf:label>
      </xf:input>
      <br />
      <xf:input ref="/var/second">
         <xf:label>Display this only if the first answer is true: </xf:label>
      </xf:input>
   </body>
</html>

In this example the type cast to boolean does not have any impact since relevant="/var/first=true()" does not work as expected. String comparison must be used.

Binding a view to a select 1

edit

This example shows how to conditionally display a view based on the value of a select1 control.

<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:xf="http://www.w3.org/2002/xforms"
   xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <title>Testing the XForms bind relevant attribute based on a select1 control.</title>
   <xf:model>
      <xf:instance>
         <data xmlns="">
            <select1value>1</select1value>
            <view1 />
            <view2 />
            <view3 />
         </data>
      </xf:instance>
      <xf:bind nodeset="/data/select1value" type="xs:decimal" />
      <xf:bind nodeset="/data/view1" relevant="/data/select1value = 1" />
      <xf:bind nodeset="/data/view2" relevant="/data/select1value = 2" />
      <xf:bind nodeset="/data/view3" relevant="/data/select1value = 3" />
   </xf:model>
   <body>
      <p>Demonstration of relevant fields.</p>
      <xf:select1 ref="/data/select1value">
         <xf:label>What view would you like to see? </xf:label>
         <br />
         <xf:item select="yes">
            <xf:label>View 1</xf:label>
            <xf:value>1</xf:value>
         </xf:item>
         <xf:item>
            <xf:label>View 2</xf:label>
            <xf:value>2</xf:value>
         </xf:item>
          <xf:item>
            <xf:label>View 3</xf:label>
            <xf:value>3</xf:value>
         </xf:item>
      </xf:select1>
      <br />
      <xf:input ref="/data/view1">
         <xf:label>First view: </xf:label>
      </xf:input>
      <xf:input ref="/data/view2">
         <xf:label>Second view: </xf:label>
      </xf:input>
      <xf:input ref="/data/view3">
         <xf:label>Third view: </xf:label>
      </xf:input>
   </body>
</html>

Note that the functionally is similar to a <xf:switch> and <xf:case> combination but no <xf:toggle> is used.

The bind statement

edit

The following line is the line that does the actual bind. You should use the instance() function whenever you are binding one instance to another.

Also note that you must compare the text of the InputIndicator (the period) to true and not the InputIndicator itself.

<xf:bind id="output_bind"
   nodeset="instance('output')/OutputValue"
   relevant="instance('input')/InputIndicator[.='true']" />

An Architecture for Conditional Views and User-Maintainable Rules

edit

Sometimes you need to have a consistent way of conditionally displaying views in a form. For example, if you want non-programmers to maintain the business logic of when a view is displayed, the binding rules to a view can be generated by an external rules engine.

To make this work you need to create a central instance that is used to control form views:

<xf:instance id="views" xmlns="">
   <data>
      <named-view-1/>
      <named-view-2/>
      <named-view-3/>
   </data>
</xf:instance>

We call this architecture one of "Named Views" because an external tool can be used to state the rules of how any named view is rendered. This will be a central location in the form that generates the view instance and the binding expressions.

Each view is wrapped in a group element that binds the group to the instance in the view:

<xf:group ref="instance('views')/named-view-1">
   <!-- these elements will be conditionally displayed based on the relevancy of named-view-1 -->
</xf:group>

<xf:group ref="instance('views')/named-view-2">
   <!-- these elements will be conditionally displayed based on the relevancy of named-view-2 -->
</xf:group>

<xf:group ref="instance('views')/named-view-2">
   <!-- these elements will be conditionally displayed based on the relevancy of named-view-3 -->
</xf:group>

The display rules for each view can then be stored in a bind statement:

<xf:bind nodeset="instance('views')/named-view-1" relevant="XPath-expression-that-returns-a-boolean-for named-view-1"/>
<xf:bind nodeset="instance('views')/named-view-2" relevant="XPath-expression-that-returns-a-boolean-for named-view-2"/>
<xf:bind nodeset="instance('views')/named-view-3" relevant="XPath-expression-that-returns-a-boolean-for named-view-3"/>

Each form can then have a "rules file" that looks similar to the following:

<form-rules-file>
   <form-id>my-form-ver-2</form-id>
   <display-rule>
      <named-view>named-view-1</named-view>
      <xpath-expression>XPath-expression-that-returns-a-boolean-for named-view-1</xpath-expression>
   </display-rule>
   <display-rule>
      <named-view>named-view-2</named-view>
      <xpath-expression>XPath-expression-that-returns-a-boolean-for named-view-2</xpath-expression>
   </display-rule>
   <display-rule>
      <named-view>named-view-2</named-view>
      <xpath-expression>XPath-expression-that-returns-a-boolean-for named-view-3</xpath-expression>
   </display-rule>
</form-rules-file>

You can then build another XForms application that allows non-programmers to maintain these rules files and and a simple transform that places the instance and bind statements in the XForms model when it is loaded. The original groups may still need to be manually added to the form but once the views are added the rules can be maintained with a separate application.

Changing Fields to Display

edit

The following example conditionally displays two zip code fields if the country code is 'usa" or displays a postal code if the field is not 'usa'.

<html
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:xf="http://www.w3.org/2002/xforms"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <head>
        <title>Testing the XForms bind relevant attribute.</title>
        <style language="text/css">
            <![CDATA[
    @namespace xf url("http://www.w3.org/2002/xforms");

     /* this allows the descenders (jpqy) to be visible in the drop down list */
     xf|select1 .xf-value {height: 1.5em;} 
     
     .ZipCode .xf-value {width:5ex}
     .ZipCodeSuffix .xf-value {width:4ex}
     .PostalCode .xf-value {width:10ex}
    ]]>
        </style>
        <xf:model>
            <xf:instance xmlns="" id="save-data">
                <data>
                    <CountryCode>usa</CountryCode>
                    <ZipCode>12345</ZipCode>
                    <ZipCodeSuffix>1234</ZipCodeSuffix>
                    <PostalCode>AB-1234</PostalCode>
                </data>
            </xf:instance>
            
            <!-- The views instance are boolean values that tell what fields are visible -->
            <xf:instance xmlns="" id="views">
                <data>
                    <DisplayZipCode/>
                    <DisplayPostalCode/>
                </data>
            </xf:instance>
            
            <!-- this rule only displays the zip code if the country code is 'usa'-->
            <xf:bind id="DisplayZipCode"
                nodeset="instance('views')/DisplayZipCode" 
                relevant="instance('save-data')/CountryCode='usa'" />
            <xf:bind id="DisplayPostalCode"
                nodeset="instance('views')/DisplayPostalCode" 
                relevant="not(instance('save-data')/CountryCode='usa')" />
            
        </xf:model>
    </head>
    <body>
        <p>Demonstration of binding Zip Code Input</p>
        
        <xf:select1 ref="instance('save-data')/CountryCode" selection="open">
            <xf:label>Country:</xf:label>
            <xf:item>
                <xf:label>USA</xf:label>
                <xf:value>usa</xf:value>
            </xf:item>
            <xf:item>
                <xf:label>Canada</xf:label>
                <xf:value>can</xf:value>
            </xf:item>
            <xf:item>
                <xf:label>Mexico</xf:label>
                <xf:value>mex</xf:value>
            </xf:item>
            <xf:item>
                <xf:label>Other</xf:label>
                <xf:value>other</xf:value>
            </xf:item>
        </xf:select1>
        <br/>
        
        <xf:group bind="DisplayZipCode">
            <xf:input ref="instance('save-data')/ZipCode" class="ZipCode">
                <xf:label>Zip Code: </xf:label>
            </xf:input>
            <xf:input ref="instance('save-data')/ZipCodeSuffix" class="ZipCodeSuffix">
                <xf:label>-</xf:label>
            </xf:input>
        </xf:group>
        <xf:input ref="instance('save-data')/LocationPostalID" bind="DisplayPostalCode" class="DisplayPostalCode">
            <xf:label>Postal Code: </xf:label>
        </xf:input>
    </body>
</html>

Delete

edit

One of the most common occurrence of conditional display is the delete function for repeating elements. Typically you do not want to delete the last one. See the XForms/Conditional_delete example.

Discussion

edit
Next Page: Show-Hide Controls | Previous Page: Switch and Case
Home: XForms