XForms/Disable Buttons

Motivation

edit

You want to remove a delete button to prevent removal of the last item of a list.

Method

edit

Create a binding rule in the model that only allows the button to be displayed if there are two or more elements.

This is very common when you have a "Delete" button that must not be visible if there is only one item in a list left.

One approach is to use the "ref" attribute of each trigger to only display if there are more than one item:

<xf:trigger ref="self::node()[count(../name) &gt; 1]">
   <xf:label>Delete Classifier</xf:label>
   <xf:delete ev:event="DOMActivate" nodeset="instance('save-data')/name" at="index('name-repeat')"/>            
</xf:trigger>

This would count the number of classifiers and display the "delete" button.

<data>
  <name>John</name>
  <name>Fred</name>
  <name>Sue</name>
</data>

Here is an example of this rule:

<xf:bind id="triggerDisplay" nodeset="display-delete" relevant="count(/data/person) &gt; 1"/>

The above uses the XPath count() function with the greater than operator. But since it must be escaped you must use the "&gt;" instead of ">".

Or alternatively the delete button is only visible if the second person record exists in the instance.

<xf:bind id="triggerDisplay" nodeset="display-delete" relevant="/data/person[2]"/>

This gets around having to count all the elements and doing a comparison. So it is a little bit more efficient for longs lists of items.

This creates a rule call "triggerDisplay" that pin relevancy to an XForms instance.

Screen Image

edit
 
Form without Delete button
 
Form with Delete button visible
edit

Load XForms Application

<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">
   <head>
     <title>Disable Delete</title>
     <xf:model>
       <xf:instance id="myInstance" xmlns="">
         <data>
           <person>
             <name>John Doe</name>
           </person>
           <display-delete/>
         </data>
       </xf:instance>
       <!-- only display the delete button if there is more than one person -->
       <xf:bind id="triggerDisplay" nodeset="display-delete"
            relevant="count(/data/person) &gt; 1"/>
         <xf:submission id="mySubmission" method="post"
            action="http://www.xformstest.org/cgi-bin/echo.sh"/>
     </xf:model>
   </head>
   <body>
     <h2>Demo of Button Disable</h2>
     <xf:repeat nodeset="person">
       <xf:input ref="name">
         <xf:label>Name: </xf:label>
       </xf:input>
     </xf:repeat>
     <hr/>
     <xf:trigger>
       <xf:label>Insert</xf:label>
       <xf:insert nodeset="instance('myInstance')/person"
            at="last()" position="after" ev:event="DOMActivate"/>
     </xf:trigger>
     <!-- This trigger is bound to the display rule -->
     <xf:trigger bind="triggerDisplay">
       <xf:label>Delete</xf:label>
       <xf:delete nodeset="instance('myInstance')/person"
           at="last()" ev:event="DOMActivate"/>
     </xf:trigger>
     <xf:submit submission="mySubmission">
       <xf:label>Submit instance</xf:label>
     </xf:submit>
   </body>
</html>

Using the Context attribute

edit

You can also use the context attribute of the trigger to tell it when to fire.

<?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">

     <head>
          <title>Disable final delete trigger.</title>
          <xf:model id="person">
               <xf:instance id="person_xml" xmlns="">
                    <person>
                         <contacts>
                              <section name="Personal">
                                   <contact/>
                              </section>
                         </contacts>
                    </person>
               </xf:instance>
          </xf:model>
     </head>
     <body>
          <xf:repeat nodeset="contacts/section" id="repeat_section">
               <fieldset>
                    <legend>
                         <xf:trigger>
                              <xf:label>-</xf:label>
                              <xf:hint>Delete this section.</xf:hint>
                              <xf:delete
                                   context="instance('person_xml')/contacts"
                                   nodeset="section[position()!=last()]"
                                   at="index('repeat_section')"
                                   ev:event="DOMActivate"/>
                         </xf:trigger>
                         <xf:input ref="./@name">
                              <xf:label>section name</xf:label>
                         </xf:input>
                    </legend>
                    <xf:repeat nodeset="contact" id="repeat_contact">
                         <xf:input ref=".">
                              <xf:label>contact</xf:label>
                         </xf:input>
                         <xf:trigger>
                              <xf:label>-</xf:label>
                              <xf:hint>Delete this contact.</xf:hint>
                              <xf:delete
                                   context="instance('person_xml')/contacts/section[index('repeat_section')]"
                                   nodeset="contact[position()!=last()]"
                                   at="index('repeat_contact')"
                                   ev:event="DOMActivate"/>                
                         </xf:trigger>
                         <xf:trigger>
                              <xf:label>+</xf:label>
                              <xf:hint>Add a new contact after this one.</xf:hint>
                              <xf:insert nodeset="." at="index('repeat_section')" position="after" ev:event="DOMActivate"/>
                         </xf:trigger>
                    </xf:repeat>
               </fieldset>
               <xf:trigger>
                    <xf:hint>Add a new section after this one.</xf:hint>
                    <xf:label>add section</xf:label>
                    <xf:insert nodeset="." at="last()" position="after" ev:event="DOMActivate"/>
               </xf:trigger>
          </xf:repeat>
     </body>
</html>

Context

edit

XForms 1.1 Specification

Optional XPath expression used to change the in-scope evaluation context for the delete element. This attribute is ignored if the bind attribute is provided. If the attribute is not given, then the default delete context is the in-scope evaluation context. Otherwise, the XPath expression is evaluated using the in-scope evaluation context, and the first node rule is applied to obtain the delete context. The delete action is terminated with no effect if the delete context is the empty node-set or if the context attribute is not given and the Node Set Binding node-set is empty.

Example of Min and Max

edit

The following disables both the Delete and Add triggers when the number of items approaches the minimum and maximum range.

In this case the XML Schemas for the Person element was the follows:

<xs:element name="Phone" minOccurs="2" maxOccurs="5">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xf="http://www.w3.org/2002/xforms">
   <head>
      <title>Phone Number Demo</title>
      <!-- used to demonstract the steps in autogeneration of XML Schemas that have repeating items -->
      <link rel="stylesheet"  type="text/css" href="person-phone.css"/>
      <xf:model>
         <xf:instance xmlns="" src="../test-input-instances/22-two-to-five-repeats.xml" id="phones"/>
         
         <!-- views are areas of the screen that are conditionally displayed -->
         <xf:instance xmlns="" id="views">
              <data>
                 <phone-delete-trigger/>
                 <phone-add-trigger/>
              </data>
         </xf:instance>
         
           <!-- only display the trigger if we have a second phone number -->
            <xf:bind id="phone-delete-trigger" 
            nodeset="instance('views')/phone-delete-trigger" 
            relevant="instance('phones')/Phone[3]"/>
            <xf:bind id="phone-add-trigger" 
            nodeset="instance('views')/phone-add-trigger" 
            relevant="count(instance('phones')/Phone) &lt; 5"/>

          <xf:submission id="save" method="post" action="save.xq" replace="all"/>
      </xf:model>
   </head>
   <body>

      <xf:label class="group-label">Phone Numbers</xf:label>
          <xf:repeat id="phone-number-repeat" nodeset="/PersonPhones/Phone">
                <xf:input ref="PhoneDescriptionText" class="PhoneDescriptionText"  id="PhoneDescriptionText"/>
                <xf:input ref="PhoneNumber" class="PhoneNumber"/>
              
              <!-- bind="phone-delete-trigger" -->
              <xf:trigger bind="phone-delete-trigger">
                  <xf:label>Delete</xf:label>
                  <!-- this deletes the currently selected phone number -->
                  <xf:delete nodeset="instance('phones')/Phone[index('phone-number-repeat')]" ev:event="DOMActivate"/>
              </xf:trigger>
              
          </xf:repeat>
          <xf:trigger bind="phone-add-trigger">
              <xf:label>Add</xf:label>
              <xf:action ev:event="DOMActivate">
                  <xf:insert nodeset="instance('phones')/Phone" at="last()" position="after"/>
                  <!-- this initialized the values of the phone number to null.  Can also use an origin attribute.   -->
                  <xf:setvalue ref="/PersonPhones/Phone[index('phone-number-repeat')]/PhoneDescriptionText" value=""/>
                  <xf:setvalue ref="/PersonPhones/Phone[index('phone-number-repeat')]/PhoneNumber" value=""/>
                  <!-- this puts the cursor in the first field of the new row we just added -->
                  <xf:setfocus control="PhoneDescriptionText"/>
              </xf:action>
          </xf:trigger>
  
    </body>
</html>


Discussion

edit
Next Page: Read Only | Previous Page: Relevant
Home: XForms