3.5.2.1.44. Table

The Table component presents information in a table view, sorts data, manages table columns and headers and invokes actions for selected rows.

gui table

XML-name of the component: table

An example of component definition in an XML-descriptor of a screen:

<data readOnly="true">
    <collection id="ordersDc" class="com.company.sales.entity.Order" view="order-with-customer">
        <loader id="ordersDl">
            <query>
                <![CDATA[select e from sales_Order e]]>
            </query>
        </loader>
    </collection>
</data>
<layout>
<table id="ordersTable" dataContainer="ordersDc" width="100%">
    <columns>
        <column id="date"/>
        <column id="amount"/>
        <column id="customer"/>
    </columns>
    <rowsCount/>
</table>
</layout>

In the example, the data element defines the collection container, which selects Order entities using JPQL query. The table element defines the data container, while columns element defines which entity attributes are used as table columns.

table elements:

  • rows – a required element if the datasource attribute is used for data binding.

    Each row can have an icon in an additional column on the left. Create an implementation of the ListComponent.IconProvider interface in the screen controller and set it for the table:

    @Inject
    private Table<Customer> table;
    
    @Subscribe
    protected void onInit(InitEvent event) {
        table.setIconProvider(new ListComponent.IconProvider<Customer>() {
            @Nullable
            @Override
            public String getItemIcon(Customer entity) {
                CustomerGrade grade = entity.getGrade();
                switch (grade) {
                    case PREMIUM: return "icons/premium_grade.png";
                    case HIGH: return "icons/high_grade.png";
                    case MEDIUM: return "icons/medium_grade.png";
                    default: return null;
                }
            }
        });
    }
  • columns – a required element defining the set of columns for a table. Element columns has the following attributes:

    • includeAll – load all the attributes from the view that is defined in dataContainer or datasource.

      In the example below, we will show all the attributes from the view used in the customersDc. If the view contains system properties, they will be shown too.

      <table id="table"
             width="100%"
             height="100%"
             dataContainer="customersDc">
          <columns includeAll="true"/>
      </table>

      If the view of the entity contains a reference attribute, this attribute will be displayed according to its instance name. If you want to show a specific attribute, it must be defined in the view as well as in the column element:

      <columns includeAll="true">
          <column id="address.street"/>
      </columns>

      If no view is specified, includeAll attribute will load all the attributes from a given entity and its ancestors.

    • exclude – comma-separated list of attributes that should not be loaded to the table.

      In the example below, we will show all the attributes excluding name and order:

      <table id="table"
             width="100%"
             height="100%"
             dataContainer="customersDc">
          <columns includeAll="true"
                   exclude="name, order"/>
      </table>

    Each column is described in a nested column element with the following attributes:

    • id − a mandatory attribute, contains the name of an entity attribute displayed in the column. Can be either an attribute of the entity from the data container or a linked entity – object graph traversal is indicated with a dot. For example:

      <columns>
          <column id="date"/>
          <column id="customer"/>
          <column id="customer.name"/>
          <column id="customer.address.country"/>
      </columns>
    • caption − an optional attribute containing the column caption. If not specified, a localized attribute name will be displayed.

    • captionAsHtml − an optional attribute defining whether HTML tags can be used in the column caption. Default value is false.

      <column id="name"
              caption="msg://role.name"
              captionAsHtml="true"/>
      role.name=<em>Name</em>
    • collapsed − an optional attribute; hides the column by default when set to true. Users can control column’s visibility using the menu available via a gui_table_columnControl button in the top right part of the table when the table’s columnControlVisible attribute is not false. By default, collapsed is false.

    • expandRatio − an optional attribute that specifies the expand ratio for each column. The ratio must be greater than or equal to 0. If some value is set for at least one column, all implicit values are ignored, and only explicitly assigned values are considered. If you set width and expandRatio attributes simultaneously, it will cause an error in the application.

    • width − an optional attribute controlling default column width. May contain only numeric values in pixels.

    • align − an optional attribute that sets text alignment of column cells. Possible values: LEFT, RIGHT, CENTER. Default is LEFT.

    • editable − an optional attribute allowing editing of the corresponding column in the table. In order for a column to be editable, the editable attribute of the entire table should be set to true as well. Changing this property at runtime is not supported.

    • sortable − an optional attribute to disable sorting of the column. Takes effect if the whole table has sortable attribute set to true (which is by default).

    • maxTextLength – an optional attribute allowing to limit the number of characters in a cell. If the difference between the actual and the maximum allowed number of characters does not exceed the 10 character threshold, the "extra" characters remain unhidden. To see the entire record, users need to click on its visible part. An example of a column with a 10 character limitation:

      gui table column maxTextLength
    • linkScreen - contains the identifier of the screen that is opened by clicking the link enabled in the link attribute.

    • linkScreenOpenType - sets the screen opening mode (THIS_TAB, NEW_TAB or DIALOG).

    • linkInvoke - invokes the controller method instead of opening the screen.

      @Inject
      private Notifications notifications;
      
      public void linkedMethod(Entity item, String columnId) {
          Customer customer = (Customer) item;
          notifications.create()
                  .withCaption(customer.getName())
                  .show();
      }
    • captionProperty - the name of an entity attribute which should be shown in the column instead of specified by id. For example, if you have a reference to the Priority entity with the name and orderNo attributes, you can define the following column:

      <column id="priority.orderNo" captionProperty="priority.name" caption="msg://priority" />

      In this case, the column will display the priority name, but the sorting of the column will be done by the priority order.

    • an optional generator attribute contains the link to a method in the screen controller that creates a visual component to be shown in table cells:

      <columns>
          <column id="name"/>
          <column id="imageFile"
                  generator="generateImageFileCell"/>
      </columns>
      public Component generateImageFileCell(Employee entity){
          Image image = uiComponents.create(Image.NAME);
          image.setSource(FileDescriptorResource.class).setFileDescriptor(entity.getImageFile());
          return image;
      }

      It can be used instead of providing an implementation of Table.ColumnGenerator to the addGeneratedColumn() method.

    • column element may contain a nested formatter element that allows you to represent the attribute value in a format different from the standard for this Datatype:

      <column id="date">
          <formatter class="com.haulmont.cuba.gui.components.formatters.DateFormatter"
                     format="yyyy-MM-dd HH:mm:ss"
                     useUserTimezone="true"/>
      </column>
  • rowsCount − an optional element adding the RowsCount component for the table; this component enables loading the table data in pages. Page size can be defined by limiting the number of records in the data container using the loader’s setMaxResults() method. Typically, this is performed by a Filter component linked to the table’s data loader. However, if there is no generic filter, this method can be called directly from the screen controller.

    RowsCount component can also show the total number of records returned by the current query from the container without extracting the records themselves. It invokes com.haulmont.cuba.core.global.DataManager#getCount when user clicks the ? icon, which results in performing a database query with the same conditions as the current query, but using a COUNT(*) aggregate function instead. The number retrieved is displayed instead of the ? icon.

  • actions − an optional element describing the actions, related to the table. In addition to custom arbitrary actions, the element supports the following standard actions, defined in the com.haulmont.cuba.gui.actions.list package: create, edit, remove, refresh, add, exclude, excel.

  • buttonsPanel – an optional element, which adds a ButtonsPanel container to show action buttons above the table.

table attributes:

  • multiselect attribute enables setting multiple selection mode for table rows. If multiselect is true, users can select multiple rows in the table using keyboard or mouse holding Ctrl or Shift keys. By default, multiple selection mode is switched off.

  • sortable attribute enables sorting data in the table. By default, it is set to true. If sorting is allowed, clicking a column header will show a gui_sortable_down/gui_sortable_up icon to the right of the column name. You can disable sorting for a particular column by using its sortable attribute.

    Table sorting can be performed differently depending on whether all the records can be placed on one page or not. If they can, sorting is performed in memory without database queries. If there is more than one page, sorting is performed in the database by sending a new query with the corresponding ORDER BY condition.

    A table column may refer to a local attribute or a linked entity. For example:

    <table id="ordersTable"
           dataContainer="ordersDc">
        <columns>
            <column id="customer.name"/> <!-- the 'name' attribute of the 'Customer' entity -->
            <column id="contract"/>      <!-- the 'Contract' entity -->
        </columns>
    </table>

    In the latter case, the database sorting will be performed by attributes defined in the @NamePattern annotation of the related entity. If the entity has no such annotation, the sorting will be performed in memory only within the current page.

    If the column refers to a non-persistent entity attribute, the database sorting will be performed by attributes defined in the related() parameter of the @MetaProperty annotation. If no related attributes are specified, the sorting will be performed in memory only within the current page.

    If the table is connected to a nested property container that contains a collection of related entities, the collection attribute must be of ordered type (List or LinkedHashSet) for table to be sortable. If the attribute is of type Set, the sortable attribute has no effect and the table cannot be sorted by users.

    You can provide your own implementation of sorting if needed.

  • presentations attribute controls the mechanism of presentations. By default, the value is false. If the attribute value is true, a corresponding icon is added to the top right corner of the table gui_presentation. The mechanism of presentations is implemented for the Web Client only.

  • If the columnControlVisible attribute is set to false, users cannot hide columns using the drop-down menu of the gui_table_columnControl button in the right part of the table header. Currently displayed columns are marked with checkmarks in the menu.

gui table columnControl all
  • If the reorderingAllowed attribute is set to false, users cannot change columns order by dragging them with a mouse.

  • If the columnHeaderVisible attribute is set to false, the table has no header.

  • If the showSelection attribute is set to false, a current row is not highlighted.

  • contextMenuEnabled attribute enables the context menu. By default this attribute is set to true. The context menu shows table actions (if any) and the System Information item containing information on the selected entity (if the user has cuba.gui.showInfo permission).

  • Setting multiLineCells to true enables multi-line display for cells containing several lines of text. In this mode, the web browser will load all the rows of the current table page at once, instead of lazy-loading the visible part of the table. It is required for proper scrolling in the Web Client. The default value is false.

  • aggregatable attribute enables aggregation for table rows. The following operations are supported:

    • SUM – calculate the sum

    • AVG – find the average value

    • COUNT – calculate the total number

    • MIN – find the minimum value

    • MAX – find the maximum value

    The aggregation element should be set for aggregated table columns with the type attribute, which sets the aggregation function. By default, only numeric data types are supported in aggregated columns, such as Integer, Double, Long and BigDecimal. The aggregated table values are shown in an additional row at the top of the table. An example of an aggregated table description:

    <table id="itemsTable" aggregatable="true" dataContainer="itemsDc">
        <columns>
            <column id="product"/>
            <column id="quantity"/>
            <column id="amount">
                <aggregation type="SUM"/>
            </column>
        </columns>
    </table>

    The aggregation element can contain the editable attribute. Setting the attribute to true in conjunction with using the setAggregationDistributionProvider() method allows developers to implement algorithms for the distribution of data between the rows of the table.

    The aggregation element can also contain the strategyClass attribute specifying a class implementing the AggregationStrategy interface (see below the example of setting an aggregation strategy programmatically).

    The valueDescription attribute defines a hint which is displayed in a popup when a user hovers the mouse cursor on the aggregated value. For the operations listed above (SUM, AVG, COUNT, MIN, MAX), popup hints are already available by default.

    A Formatter can be specified to display the aggregated value in the format other than the standard for this Datatype:

    <column id="amount">
        <aggregation type="SUM">
            <formatter class="com.company.sample.MyFormatter"/>
        </aggregation>
    </column>

    The aggregationStyle attribute allows you to specify the location of the aggregation row: TOP or BOTTOM. TOP is used by default.

    In addition to the operations listed above, you can define a custom aggregation strategy by implementing the AggregationStrategy interface and passing it to the setAggregation() method of the Table.Column class inside the AggregationInfo instance. For example:

    public class TimeEntryAggregation implements AggregationStrategy<List<TimeEntry>, String> {
        @Override
        public String aggregate(Collection<List<TimeEntry>> propertyValues) {
            HoursAndMinutes total = new HoursAndMinutes();
            for (List<TimeEntry> list : propertyValues) {
                for (TimeEntry timeEntry : list) {
                    total.add(HoursAndMinutes.fromTimeEntry(timeEntry));
                }
            }
            return StringFormatHelper.getTotalDayAggregationString(total);
        }
        @Override
        public Class<String> getResultClass() {
            return String.class;
        }
    }
    AggregationInfo info = new AggregationInfo();
    info.setPropertyPath(metaPropertyPath);
    info.setStrategy(new TimeEntryAggregation());
    
    Table.Column column = weeklyReportsTable.getColumn(columnId);
    column.setAggregation(info);
  • editable attribute enables switching the table to in-place editing mode. In this mode, the columns with editable = true attribute show components to edit the attributes of the corresponding entity.

    The component type for each editable column is selected automatically based on the type of the corresponding entity attribute. For example, for string and numeric attributes, the application will use TextField, for DateDateField, for lists – LookupField, for links to other entities – PickerField.

    For a Date type editable column, you can additionally define dateFormat or resolution attributes similar to the ones described for the DateField.

    optionsContainer and captionProperty attributes can be additionally defined for an editable column showing a linked entity. If optionsContainer is set, the application will use LookupField instead of PickerField.

    Custom configuration (including editing) of a cell can be performed using Table.addGeneratedColumn() method – see below.

  • In Web Client with a Halo-based theme, stylename attribute enables setting predefined styles to the Table component either in the XML descriptor or in the screen controller:

    <table id="table"
           dataContainer="itemsDc"
           stylename="no-stripes">
        <columns>
            <column id="product"/>
            <column id="quantity"/>
        </columns>
    </table>

    When setting a style programmatically, select one of the HaloTheme class constants with the TABLE_ prefix:

    table.setStyleName(HaloTheme.TABLE_NO_STRIPES);

    Table styles:

    • borderless - removes the outer border of the table.

    • compact - reduces the white space inside the table cells.

    • no-header - hides the table column headers.

    • no-horizontal-lines - removes the horizontal divider lines between the table rows.

    • no-stripes - removes the alternating row colors.

    • no-vertical-lines - removes the vertical divider lines between the table columns.

    • small - small font size and reduced the white space inside the table cells.

Methods of the Table interface:

  • addColumnCollapsedListener() method is useful for tracking the columns visibility with the help of the ColumnCollapsedListener interface implementation.

  • getSelected(), getSingleSelected() return instances of the entities corresponding to the selected rows of the table. A collection can be obtained by invoking getSelected(). If nothing is selected, the application returns an empty set. If multiselect is disabled, it is more convenient to use getSingleSelected() method returning one selected entity or null, if nothing is selected.

  • addSelectionListener() enables tracking the table rows selection. For example:

    customersTable.addSelectionListener(customerSelectionEvent ->
            notifications.create()
                    .withCaption("You selected " + customerSelectionEvent.getSelected().size() + " customers")
                    .show());

    You can also implement selection tracking by subscription to the corresponding event:

    @Subscribe("customersTable")
    protected void onCustomersTableSelection(Table.SelectionEvent<Customer> event) {
        notifications.create()
                .withCaption("You selected " + customerSelectionEvent.getSelected().size() + " customers")
                .show();
    }

    The origin of the SelectionEvent can be tracked using isUserOriginated() method.

  • addGeneratedColumn() method allows you to define custom representation of data in a column. It takes two parameters: identifier of the column and an implementation of the Table.ColumnGenerator interface. Identifier can match one of the identifiers set for table columns in XML-descriptor – in this case the new column is inserted instead of the one defined in XML. If the identifier does not match any of the columns, a new column is added to the right.

    generateCell() method of the Table.ColumnGenerator interface is invoked for each row of the table. The method receives an instance of the entity displayed in the corresponding row. generateCell() method should return a visual component which will be displayed in the cell.

    Example of using the component:

    @Inject
    private GroupTable<Car> carsTable;
    @Inject
    private CollectionContainer<Car> carsDc;
    @Inject
    private CollectionContainer<Color> colorsDc;
    @Inject
    private UiComponents uiComponents;
    @Inject
    private Actions actions;
    
    @Subscribe
    protected void onInit(InitEvent event) {
        carsTable.addGeneratedColumn("color", entity -> {
            LookupPickerField<Color> field = uiComponents.create(LookupPickerField.NAME);
            field.setValueSource(new ContainerValueSource<>(carsTable.getInstanceContainer(entity), "color"));
            field.setOptions(new ContainerOptions<>(colorsDc));
            field.addAction(actions.create(LookupAction.class));
            field.addAction(actions.create(OpenAction.class));
            return field;
        });
    }

    In the example above, all cells within the color column in the table show the LookupPickerField component. The component will save its value into the color attribute of the entity instance which is displayed in the corresponding row.

    The getInstanceContainer() method returning container with the current entity should be used only for data binding of components created when generating table cells.

    If you want to display just dynamic text, use special class Table.PlainTextCell instead of the Label component. It will simplify rendering and make the table faster.

    If addGeneratedColumn() method receives the identifier of a column which is not declared in XML-descriptor, the header for the new column to be set as follows:

    carsTable.getColumn("colour").setCaption("Colour");

    Consider also using a more declarative approach with the generator XML attribute.

  • requestFocus() method allows you to set focus on the concrete editable field of a certain row. It takes two parameters: an entity instance which identifies the row and the column identifier. Example of requesting a focus:

    table.requestFocus(item, "count");
  • scrollTo() method allows you to scroll table to the concrete row. It takes one parameter: an entity instance identifying the row.

    Example of scrolling:

    table.scrollTo(item);
  • setCellClickListener() method can save you from adding generated columns with components when you need to draw something in cells and receive notifications when a user clicks inside these cells. The CellClickListener implementation passed to this method receives the selected entity and the column identifier. The cells content will be wrapped in span element with cuba-table-clickable-cell style which can be used to specify the cell representation.

    Example of using CellClickListener:

    @Inject
    private Table<Customer> customersTable;
    @Inject
    private Notifications notifications;
    
    @Subscribe
    protected void onInit(InitEvent event) {
        customersTable.setCellClickListener("name", customerCellClickEvent ->
                notifications.create()
                        .withCaption(customerCellClickEvent.getItem().getName())
                        .show());
    }
  • You can use the setAggregationDistributionProvider() method to specify the AggregationDistributionProvider that defines the rules for distributing the aggregated value between rows in a table. If the user enters a value in an aggregated cell, it is distributed to the constituent cells according to a custom algorithm. Supported only for the TOP aggregation style. To make aggregated cells editable, use the editable attribute of the aggregation element.

    When creating a provider, you should use the AggregationDistributionContext<E> object, which contains the data needed to distribute the aggregated value:

    • Column column where total or group aggregation was changed;

    • Object value − the new aggregation value;

    • Collection<E> scope − a collection of entities that will be affected by changed aggregation;

    • boolean isTotalAggregation shows total aggregation changed, or it was group aggregation.

      As an example, consider a table that represents a budget. The user creates budget categories and sets for each of them the percentages according to which the amount of income should be distributed. Next, the user sets the total amount of income in the aggregated cell; after that, it distributes by category.

      An example of table configuration in an XML-descriptor of a screen:

      <table id="budgetItemsTable"
             width="100%"
             dataContainer="budgetItemsDc"
             aggregatable="true"
             editable="true"
             showTotalAggregation="true">
              ...
          <columns>
              <column id="category"/>
              <column id="percent"/>
              <column id="sum">
                  <aggregation editable="true"
                               type="SUM"/>
              </column>
          </columns>
              ...
      </table>

      An example in a screen controller:

      budgetItemsTable.setAggregationDistributionProvider(context -> {
          Collection<BudgetItem> scope = context.getScope();
          if (scope.isEmpty()) {
              return;
          }
      
          double value = context.getValue() != null ?
                  ((double) context.getValue()) : 0;
      
          for (BudgetItem budgetItem : scope) {
              budgetItem.setSum(value / 100 * budgetItem.getPercent());
          }
      });
  • The getAggregationResults() method returns a map with aggregation results, where map keys are table column identifiers, and values are aggregation values.

  • The setStyleProvider() method enables setting table cell display style. The method accepts an implementation of Table.StyleProvider interface as a parameter. getStyleName() method of this interface is invoked by the table for each row and each cell separately. If the method is invoked for a row, the first parameter contains the entity instance displayed by the row, the second parameter is null. If the method is called for a cell, the second parameter contains the name of the attribute displayed by the cell.

    Example of setting a style:

    @Inject
    protected Table customersTable;
    
    @Subscribe
    protected void onInit(InitEvent event) {
        customersTable.setStyleProvider((customer, property) -> {
            if (property == null) {
            // style for row
            if (hasComplaints(customer)) {
                return "unsatisfied-customer";
            }
        } else if (property.equals("grade")) {
            // style for column "grade"
            switch (customer.getGrade()) {
                case PREMIUM: return "premium-grade";
                case HIGH: return "high-grade";
                case MEDIUM: return "medium-grade";
                default: return null;
            }
        }
            return null;
        });
    }

    Then the cell and row styles set in the application theme should be defined. Detailed information on creating a theme is available in Themes. For web client, new styles are defined in the styles.scss file. Style names defined in the controller, together with prefixes identifying table row and column form CSS selectors. For example:

    .v-table-row.unsatisfied-customer {
      font-weight: bold;
    }
    .v-table-cell-content.premium-grade {
      background-color: red;
    }
    .v-table-cell-content.high-grade {
      background-color: green;
    }
    .v-table-cell-content.medium-grade {
      background-color: blue;
    }
  • addPrintable() method enables setting a custom presentation of the data within a column when exporting to an XLS file via the excel standard action or directly using the ExcelExporter class. The method accepts the column identifier and an implementation of the Table.Printable interface for the column. For example:

    ordersTable.addPrintable("customer", new Table.Printable<Customer, String>() {
        @Override
        public String getValue(Customer customer) {
            return "Name: " + customer.getName;
        }
    });

    getValue() method of the Table.Printable interface should return data to be displayed in the table cell. This is not necessarily a string – the method may return values of other types, for example, numeric data or dates, which will be represented in the XLS file accordingly.

    If formatted output to XLS is required for a generated column, an implementation of the Table.PrintableColumnGenerator interface passed to the addGeneratedColumn() method should be used. The value for a cell in an XLS document is defined in the getValue() method of this interface:

    ordersTable.addGeneratedColumn("product", new Table.PrintableColumnGenerator<Order, String>() {
        @Override
        public Component generateCell(Order entity) {
            Label label = uiComponents.create(Label.NAME);
            Product product = order.getProduct();
            label.setValue(product.getName() + ", " + product.getCost());
            return label;
        }
    
        @Override
        public String getValue(Order entity) {
            Product product = order.getProduct();
            return product.getName() + ", " + product.getCost();
        }
    });

    If Printable presentation is not defined for a generated column in one way or another, then the column will either show the value of corresponding entity attribute or nothing if there is no associated entity attribute.

  • The setItemClickAction() method allows you to define an action that will be performed when a table row is double-clicked. If such action is not defined, the table will attempt to find an appropriate one in the list of its actions in the following order:

    • The action assigned to the Enter key by the shortcut property

    • The edit action

    • The view action

      If such action is found, and has enabled = true property, the action is executed.

  • The setEnterPressAction() allows you to define an action executed when Enter is pressed. If such action is not defined, the table will attempt to find an appropriate one in the list of its actions in the following order:

    • The action defined by the setItemClickAction() method

    • The action assigned to the Enter key by the shortcut property

    • The edit action

    • The view action

    If such action is found, and has enabled = true property, the action is executed.

The appearance of the Table component can be customized using SCSS variables with $cuba-table-* prefix. You can change these variables in the visual editor after creating a theme extension or a custom theme.