ShowTable of Contents
This post is broken down into two sections; Advanced Table Features and Performance Concerns. I will try to provide explanations for each feature in a manner that can be re-used in your own form implementations. The method of implementation is also considered the current best practice.
Advanced Table Features
I have attached two versions of the same form. This one form demonstrates all of the advanced features covered in this article. This article references the following data instance:
1: <xforms:instance id="cg_assessment" xmlns="">
4: <ChecklistCollection name="QWERTY" totalRows="">
5: <Checklist name="A" num="1" showFrom="1" showNum="1" state="v" totalRows="">
6: <EnablingObjective description="AA" num="" state="v">
7: <Criterion description="Red" num=""></Criterion>
8: <Criterion description="Green" num=""></Criterion>
9: <Criterion description="Blue" num=""></Criterion>
Add and Delete Rows
Most implementations of a table will require the ability for the user to add and delete rows. There are several different ways that this functionality can be added. The most versatile method contains the following characteristics:
- The user can delete all the rows
- New rows are empty when added
- User can delete any row
- New rows are inserted at the end of the table
To add a row to the table we just need to insert a new empty element in the data instance. In this form we accomplish that by copying a template row of the data instance (stored in a separate instance) into the main instance. By using a template row we enable the user to delete all the rows and our table will still function (other implementations copy an existing row and then clear the data using xforms:setvalue).
<xforms:insert at="index('TABLE11')" context="." nodeset="EnablingObjective[last()]" origin="instance('template')/AssessmentType/ChecklistCollection/Checklist/EnablingObjective" position="after"></xforms:insert>
Understanding the attributes of the xforms:insert can be the most challenging part of working with tables. Keep the basic premise in mind as we go through these attributes; we are copying the template row and inserting it into the desired location of the main data instance.
origin = This is a reference to the data element that we are copying, it takes precedence over the nodeset. Note how this points to a different data instance where the template row is stored.
nodeset = Defines what is being copied (ignored because origin is defined) and where the new record is being placed (after the current position).
context = The context is required if all the rows are deleted, it allows us to create elements if they don't exist. It uses the current location wildcard (.).
at / position = These two are used together. The copied data will be inserted after
the current index
of the table.
The delete operation is much simpler.
<xforms:delete at="1" nodeset="."></xforms:delete>
By declaring a nodeset using the current index (.) XPath returns only the current row; we can then set "at" to 1 since there is only one node in the result set.
Alternating Row Colors
Regardless of which version you choose to implement (panes vs boxes) the compute is the same for configuring alternating row colors, the only difference is where it is placed.
<bgcolor compute="mod(xforms.getPosInSet(), '2') == '0' ? '#99DAF9' : '#D7F2FF'">transparent</bgcolor>
We use a function that was provided to help you identify where the current object is in the nodeset. By using the mod function (division that returns the remainder) we can set all the even rows to one color and the odd rows to another.
The important thing to remember when nesting tables is that the nested references need to be defined relative to the parent. If you specify an absolute path then the rows will not reference their own unique data elements. In the following code snippet I have pulled out just the xforms:group and xforms:repeat references to make it easier to see how the nesting works. The first reference is absolute, each subsequent reference is relative to the current location.
<xforms:repeat id="TABLE1" nodeset="ChecklistCollection">
<xforms:repeat id="TABLE11" nodeset="Checklist">
<xforms:repeat id="TABLE12" nodeset="EnablingObjective">
Some people have asked about giving the form user the ability to re-order the rows of a table. This implementation uses a buttons to move the selected row up or down 1 position.
The premise for both buttons is exactly the same; when the button is clicked copy the current row, insert it into its new location and delete the original row.
<xforms:insert at="index('TABLE11')-1" nodeset="../Checklist" origin="." position="before"></xforms:insert>
<xforms:delete at="1" nodeset="."></xforms:delete>
In this example, I had to add an additional action, xforms:setfocus. If the focus does not leave the current table then the first row change works but all subsequent ones fail. I set the focus to the parent table.
Minimize and Maximize Table Row
This feature allows the user to minimize a table, hiding all of the content contained in each row.
We can accomplish this very easily by setting up a bind that sets the relevant of the row to "false". When you set the relevant of a data element to false the node (and any children) are omitted from a submission and any UI linked to it will also disappear. The binds that we configure watch a special flag (@state) that will define the behavior (minimized/maximized).
This first bind controls the relevance of the EnablingObjective table (the green rows).
<xforms:bind nodeset="instance('cg_assessment')/AssessmentType/ChecklistCollection/Checklist[position()]/EnablingObjective" relevant="boolean-from-string(if(../@state='>', 'false', 'true'))"></xforms:bind>
This second bind controls the relevance of the Criterion table (the orange/yellow rows).
<xforms:bind nodeset="instance('cg_assessment')/AssessmentType/ChecklistCollection/Checklist[position()]/EnablingObjective[position()]/Criterion" relevant="boolean-from-string(if(../@state='>', 'false', 'true'))"></xforms:bind>
The second part to this functionality is to toggle the special flag that the bind monitors. Every time we click the button we will change that flag's value. Using xforms:setvalue we can set the @state, if the current value is maximized ("v") then set it to minimized (">"), otherwise set it to maximized ("v").
<xforms:setvalue ref="." value="if(.='v','>', 'v')"></xforms:setvalue>
Limit Number of Visible Row
The best use of a table is as a window into a large data set, Lotus Forms is not optimized to render large data sets. It is not a spreadsheet application and should not be used as one. Both of these forms demonstrate how this can be accomplished. When the form is configured in this way our instance data can be significantly larger then if the table was displaying all the rows. You can experiment with this form by changing the number of visible rows to observe the performance comparison.
There are a few pieces necessary to implement a scrolling table:
1. Variables to control how many rows are shown and the starting point. These variables can be placed anywhere in the instance data, for this example they have been created as attributes to the main data instance. If you do not want this presentation-altering data to be transmitted with your data then you could create a separate instance that will just be used to contain the variables.
showFrom - is the starting index of the rows that will be shown
showNum - is the number of rows that will be shown
totalRows - is the total number of rows in the nodeset
<Checklist name="A" num="1" showFrom="1" showNum="1" state="v" totalRows="">
2. Buttons to change the table variables
Within each button we have three xforms events executed on the DOMActivate (button click) event. The first will change the starting index, every time you click one of the navigation buttons it will either add or subtract the showNum from the current index.
We recently re-designed our table implementation to improve performance by reducing the number of times the data and screen get refreshed. Because of this change the UI will not update after executing the setvalue, therefore the rebuild and recalculate are added to force a table refresh.
Each button has a different if clause to insure that the button does not do anything if we have reached either the beginning or end of the nodeset.
Move Up Through Record Set
<xforms:setvalue ref="@showFrom" value="if(../@totalRows >= (. + ../@showNum),(. + ../@showNum),.)"></xforms:setvalue>
Move Down Through Record Set
<xforms:setvalue ref="@showFrom" value="if((. - ../@showNum) >= 1,(. - ../@showNum),1)"></xforms:setvalue>
3. Define the table's nodeset to respect the variables
Now we use a more complicated nodeset definition to change how the table behaves. We only want to show the records that are greater than the starting index and less than the index plus the number of rows we want to show.
<xforms:repeat id="Objective_TBL" nodeset="EnablingObjective[position() >= ../@showFrom and position() < (../@showFrom + ../@showNum)]">
Do Not Use Panes
This post contains two versions of the same form; one uses an XForms pane to wrap the content in each table row and the other does not. The implementation choice that you make has a significant performance implication as the table grows in size.
If you require the use of a table that may have more than 10 rows or may contain a nested table then you should not use a pane in your table. I realize that this is a challenging demand because the table wizard does not give you an option to build a table without panes. If you use the table wizard then you may need to manually remove the panes after the table is built. Here are a few pointers to removing the panes from your table-wizard table:
1. Open the Source panel.
2. Start at the top of the form and locate the first ""
4. Now you have to locate the end of the group and pane objects. Scroll down through your form, or use "find" to locate the closing tag.
5. We have to comment out the closing group tag, the pane itemlocation and the pane closing tag:<!--/xforms:group>
6. Now test the form to make sure you didn't break the table!