Datatable

This page is obsolete and available for historical purposes only.

Status

As of Orbeon Forms 4.0, the datatable component is deprecated. It is entirely removed from Orbeon Forms 4.5. There is no doubt that the datatable is a useful component, however, it has been mostly developed in 2009, quickly grew to be a very complex component, and unfortunately, since 2009 we have failed to properly maintain it. Since the datatable isn't used by Form Builder or Form Runner, and hasn't been used much by customers either, we can't justify making the significant investment that would required for us to maintain it.

Overview

The datatable component displays tabular information in a spreadsheet like fashion and supports re-sizable columns, scrolling, sort, pagination and more... Its look an feel is similar to the datatable YUI component to which it borrows its CSS but its implementation is mostly based on XForms rather than JavaScript.


Using a datatable component is very similar to embedding an xforms:repeat control within an (X)HTML table. It's basically just a matter of replacing the xhtml:table element by the fr:datatable bound element and adding a few attributes to control the features of the datatable. The table displayed above can be defined as:

<fr:datatable scrollable="both" width="800px" height="500px">
    <thead>
        <tr>
            <th fr:sortable="true" fr:resizeable="true">Date</th>
            <th fr:sortable="true" fr:resizeable="true">Author</th>
            <th fr:sortable="true" fr:resizeable="true">Title</th>
        </tr>
    </thead>
    <tbody>
        <xforms:repeat ref="/atom:feed/atom:entry">
            <tr>
                <td>
                    <xf:output ref="atom:published" xxforms:format="format-dateTime
                        (., '[M01]/[D01]/[Y] - [h01]:[m01]:[s01]')"/>
                </td>
                <td>
                    <a href="{atom:author/atom:uri}">
                        <xf:output value="atom:author/atom:name"/>
                    </a>
                </td>
                <td>
                    <xf:output value="atom:title"/>
                </td>
            </tr>
        </xforms:repeat>
    </tbody>
</fr:datatable>

Attributes

You configure the datatable with attributes; global attributes go on the <fr:datatable> element, while column-specific attributes go on the corresponding <xhtml:th> element (or or <xhtml:td> when the datatable is simplified; more on this see below):


Global attributes 

The attributes that control properties which are global to the datatable are located on the <fr:datatable> bound element. Belonging to that element, they do not need a prefix to be distinguished and are left unqualified. These attributes are:
  • scrollable: defines in which directions the datatable is scrollable and can take the values "vertical", "horizontal" and "both". By default, datatables are not scrollable. For scrollable datatables:
    • height: defines the height of the datatable using the CSS syntax (write "500px" rather than "500"). This attribute is mandatory for datatables that are vertically scrollable. It isn't used for datatables without vertical scroll bars due to issues in web browser compatibility.
    • width: defines the width of the datatable using the CSS syntax (write "800px" rather than "800"). This attribute is mandatory for datatables that are horizontally scrollable. For datatables that have no horizontal scroll bar, it defines the initial width and the table is resized when one of its columns is resized.
    • innerTableWidth: for horizontally scrollable datatables, forces the width of the internal table that would otherwise be computed to minimize the table height.
  • paginated: defines if the datatable should be paginated and can take the values "true" or "false" (default). For paginated datatables:
  • rowsPerPage: defines the number of rows per page (default = 10)
  • maxNbPagesToDisplay: defines the maximum number of pages to display on the page selectors (m) as follows: if maxNbPagesToDisplay is odd, then m = maxNbPagesToDisplay, otherwise mmaxNbPagesToDisplay + 1. If you don't specify the maxNbPagesToDisplay attribute, then by default all the pages are displayed. If the total number of pages is t, the maximum number of pages to display is m, and the current page c then:

    • If c <= m - 4 (you are in the first m - 4 pages), you see the first m - 4 pages, an ellipsis, and the last page number:
    • If t - c <= m - 4 (you are in the last m - 4 pages), you see 1, an ellipse, and the last m - 4 pages:
    • Otherwise (you are "in the middle"), you see 1, an ellipse, (m - 5)/4 pages before the current page, the current page, and(m - 5)/4 pages after the current page, and ellipse, and the last page:
The purpose of this algorithm is that the page numbers that are displayed stay centered on the current page as much as possible. Since this centering works only with odd numbers, the value of the maxNbPagesToDisplay is silently rounded to the next odd value (if you specify a maxNbPagesToDisplay equal to 6, the datatable will display a maximum of 7 page numbers).
  • sort-modeinternal (default) if the datatable should support the sorting by itself, external if it's handled by the XForms application outside of the datatable.
  • pagination-mode — internal (default) if the datatable should support the sort and/or pagination, external if it's handled by the XForms application outside of the datatable. When set to external:
    • page: XPath expression that calculates the current page number.
    • nbPages: XPath expression that calculates the number of pages.
    • modal: the value is either true or false (default), and indicates whether the datatable displays the "spinner". This will create the same visual effect you get with xforms:trigger and xforms:submit using the xxforms:modal="true" attribute. This is particularly useful if on fr-goto-page or fr-update-sort you are calling external services, and want to give a visual cue to users that data is being loaded.

  • sortAndPaginationMode (deprecated) — You can set this attribute to either internal (default) or external. This is a shortcut for setting both the sort-mode and pagination-mode to same value. If you use this attribute, you can't also set sort-mode and pagination-mode.
    • loading: XPath expression provided by the application that returns true if the data displayed by the datatable is still loading and if a loading indicator must be shown.

Experimental / Development Attributes

  • dynamic: temporary (as of October 2009) indicator to force to use the new algorithm that supports dynamic column sets (see RFE #313661) even when there are no dynamic column sets in the datatable (default = false, i.e. use the previous, safer, algorithm).
  • debug: when set to true, display the content of the datatable local instance (default = false). Only supported by the dynamic column sets algorithm.

Column attributes

The attributes that control column properties are located on the <xhtml:th> (or <xhtml:td> when the datatable is simplified, see below). Belonging to an XHTML element, they need to be prefixed to be distinguished from XHTML attributes. These attributes are:
  • fr:sortable: defines if the column is sortable. Can take the values "true" or "false" (default).
  • fr:sortType: overrides the guess done by the component which normally determines the sort type from the datatype bound to the node displayed in the column. Can take the values "text" (default) and "number". Note that the datatable works with instance values and can reliably sort ISO dates with the "text" sort type even if these dates are displayed in a more user friendly format.
  • fr:sorted: declares that a column is already sorted so that the component can adapt its display. Can take the values "ascending" or "descending".
  • fr:sortKey: define the XPath expression to be used as a sort key. When not specified, this key is derived from xforms:output controls found in the column.
  • fr:resizeable: defines if the column is resizeable. Can take the values "true" ot "false"(default).
  • When you handle sorting (sortAndPaginationMode="external"), on the sortable columns add:
    • fr:sortMessage: this XPath expression provided by the application to the datatable returns the message to display when the mouse hoovers over the column header. Typically, depending on how the columns is currently sorted, you will return a message like "Click to sort descending" or "Click to sort ascending".
    • class: use an AVT to return either yui-dt-asc (for ascendant sorting, yui-dt-desc (for descendant sorting), or the empty string (if the column is not presently sorted).

Header Row Attribute

  • fr:master: when set to true this attribute defines that the header row should be considered as the "master row".  Even though most datatables have headers with a single row, it is possible to have headers with multiple rows and in that case, the click-able labels (for sortable columns) and resizers handles are located in one of the rows which cells must not have colspan or rowspan attribute. This row is called the "master row". By default, the master row is the last row of the header and the fr:master attribute is useful to specify when another row should be used as the master row.  

Events

Events dispatched by the datatable
fr-selection-changed This event is dispatched when the selected node changes for whatever reason (initialization, click, xforms:setindex, repeat nodeset update, sort order and page update, ...). The following contexts are sent by this event:
  • index is the new index in the current page with the current sort order
  • selected is the new selected node
Note that when this node can be returned within the result of the oxf:sort() function and that its parents and siblings may not be the same than in the original datatable repeat nodeset.

  fr-goto-page This event is dispatched with both external and internal paging when users switch to another page:
  • With external paging, you will want to handle this event and load the data for the new page, as requested by users.
  • With internal paging, in most cases you won't need to handle this event. However, you could handle it and store what page users are on, so you can implement a feature like "select all items on the current page".
The value of the new page number is passed as the fr-new-page context.

  fr-update-sort Dispatched when the sort is handled externally to the datatable and the sort order needs to be updated. The value of the column index where the user has clicked is passed as the fr-column context.

Event you can dispatch to the datatable
fr-goto-page With internal paging (i.e. when paging is handled by the datatable for you), you can instruct the datatable to switch to a given page by sending it this event. When dispatching the event, set the fr-new-page property to page number you'd like to go to. For instance:

<xforms:dispatch ev:event="DOMActivate"
        target="my-datatable" name="fr-goto-page">

    <xxforms:context name="fr-new-page" select="2"/>
</xforms:dispatch>


Simplifications

The component supports two syntactical simplifications or abbreviations.
  • xforms:repeat/xhtml:tr can be replaced by xhtml:tr/@repeat-ref [SINCE 2012-04-06] (or for backward compatibility xhtml:tr/@repeat-nodeset). The fragment:
<xforms:repeat ref="/atom:feed/atom:entry">
    <xhtml:tr>
        ...
    </xhtml:tr>
</xforms:repeat>

Can be written:

<xhtml:tr repeat-ref="/atom:feed/atom:entry">
    ...
</xhtml:tr>

  • Headers can be generated from cells labels.
It is very common that columns consist of a single <xforms:output> control. In that case, it can be considered simpler to provide an <xforms:label> rather than explicitly define table headers. Our example could be written:

<fr:datatable scrollable="both" width="800px" height="500px">
     <tbody>
         <tr repeat-ref="/atom:feed/atom:entry">
             <td fr:sortable="true" fr:resizeable="true">
                 <xf:output ref="atom:published" xxforms:format="format-dateTime
                         (., '[M01]/[D01]/[Y] - [h01]:[m01]:[s01]')">

                     <xf:label>Date</xf:label>
                 </xf:output>
             </td>
             <td fr:sortable="true" fr:resizeable="true">
                 <a href="{atom:author/atom:uri}">
                     <xf:output value="atom:author/atom:name">
                         <xf:label>Author</xf:label>
                     </xf:output>
                 </a>
             </td>
             <td fr:sortable="true" fr:resizeable="true">
                 <xf:output value="atom:title">
                     <xf:label>Title</xf:label>
                 </xf:output>
             </td>
         </tr>
     </tbody>
 </fr:datatable>

This is more compact and arguably easier to read than the original form because the definition of each column is grouped together instead of being split between the body and the header.

Restrictions

The datatable component has been designed to look like the syntax of an ordinary (X)HTML table and will usually behave as expected. That being said, under the cover, the component needs to analyze the table structure and to implement features such as sort and pagination it has to rewrite the ref expression of the main xforms:repeat with pieces of XPath expressions cut from XForms controls fetched from table cells.

This is a fairly complex task that is implemented in XBL and XSLT and that imposes several constraints to the structure of the datatable and the XPath expressions that can be used. As a result, the datatable imposes the following restrictions on the table structure after the simplifications have been processed:
  • The content of the header and body rows must be composed of xhtml:th (for the header) and xhtml:td (for the body) elements and of xforms:repeat elements enclosing xhtml:th or xhtml:td elements. No other elements are supported. For instance, you cannot have an xforms:group around xhtml:th or xhtml:td.
  • The xforms:repeat can be freely mixed with xhtml:th (in header) and xhtml:td (in body) elements but there should be an exact and immediate match between the structure of the table body and headers.
  • The number of columns should be the same in header and body rows.
  • There must be exactly one header row in the table header and no other content.
  • There must be exactly one xforms:repeat embedded in the table body and no other content.
Other important restrictions are dictated by the XPath "cut and paste" algorithm involved when the datatable supports sort and pagination internally. These restrictions can be summarized by the fact that the expressions used in XForms controls that will be used as sort keys must be valid in an element that is external to the xforms:repeat where they appear. The main effect of this issue is that they cannot use local variables (see issue #314323).

Sort and paginations are implemented in XForms by rewriting the <xforms:repeat> nodeset expression. This means that context functions such as index() or position() are evaluated in the context of the sorted and paginated node set.

Loading indicator

The datatable is good candidate on which to apply the delay expensive submissions tuning technique. The datatable offers a way for you to indicate that the data is being loaded using the optional loading attribute. The value of this attribute is an XPath expression, and its result is interpreted as a boolean value. Typically, you will:
  1. Use a node in an instance (say <loading>) a way to keep track of whether the data is being loaded or has already been loaded. You initially store true in that node (e.g. <loading>true</loading>).
  2. You make your datatable point to that node (e.g. <fr:datatable loading="/path/to/loading = 'true'"/>).
  3. You delay the expensive submission until after the form is loaded.
  4. When the submission is done, you set the content of the node to false.
With this in place, while the data is loading, the datatable will show a spinning icons, as in:


The datatable CSS uses the class fr-datatable-is-loading to set a background image (in this case a spinning icon) for the central area of the datatable. You can change this by overriding in your CSS the default style defined for the fr-datatable-is-loading.

Properties

Browsers can take a noticeable amount of time to render datatables, up to seconds for larger tables on slower browsers. By default, datatables are rendered as soon as they are potentially visible (relevant, not in a non-current case, or a non-displayed dialog). If you set the following property to true (its default is false), datatables rendering is delayed until they become at least partially visible on the screen.

<property as="xs:boolean" name="oxf.xforms.xbl.fr.datatable.init-in-viewport" value="true"/>

In cases where users have to scroll to reach datatables, you may improve the user experience by delay the rendering until users reach the datatables while scrolling. This can make a significant difference in cases where you have several datatables on the page: instead of introducing a longer initial wait time, users will experience shorter wait times as they scroll down and reach each datatable.

Displaying a row number in internal mode

In some cases, you want to show, maybe in the first column, the current row number. This row number needs to take paging and sorting into account. For instance, if you have 4 items per page and you are on the second page, whatever the sorting is, you'd like the lines to be numbered 5, 6, 7, and 8, as in this screenshot:


For this, you need two constructs: position() tells you the current line on the current page, and $fr-dt-page tells you the page number. Based on those two constructs, inside your <xhtml:tbody> add a column:

<xhtml:td>
    <xforms:output value="position() + ($fr-dt-page - 1)*4"/>
</xhtml:td>

And obviously, you'll want to add a corresponding <xhtml:th/> inside your <xhtml:thead> to keep the number of columns in your table headers in sync with the table body.

See also