dataGrid

A dataGrid displays structured data in rows and columns and supports efficient browsing of large data sets.

XML Element

dataGrid

Java Class

DataGrid

Overview

Data grid

Basics

To create the component, use the dataGrid XML element and bind it to a data container. The component supports both collection and key-value containers. Then, specify which attributes from the container you want to display:

  • To display all attributes as specified in the fetch plan, add the columns element with includeAll = true. To exclude unnecessary attributes, list them in the exclude attribute separated by commas.

  • To display only certain attributes, add the columns element nesting individual column elements for each attribute you want to include.

  • If the fetch plan contains a reference attribute, this attribute will be displayed according to its instance name. To display a specific attribute, add it explicitly to the fetch plan as well as in the column element.

The sample binds the grid to a collection container populated by usersDl. The XML chooses the entity attributes to display, while the controller supplies generated data to the loader:

XML
<data readOnly="true">
    <collection id="ordersDc"
                class="io.jmix.uisamples.entity.Order">
        <fetchPlan extends="_local">
            <property name="customer" fetchPlan="_local"/>
        </fetchPlan>
        <loader id="ordersLoader">
            <query>
                <![CDATA[select e from uisamples_Order e order by e.date]]>
            </query>
        </loader>
    </collection>
</data>
<facets>
    <dataLoadCoordinator auto="true"/>
</facets>
<layout>
    <checkboxGroup id="dataGridSettingsGroup" label="Settings" themeNames="horizontal"/>
    <dataGrid id="dataGrid" width="100%" minHeight="20em"
              dataContainer="ordersDc">
        <columns>
            <column property="date"/>
            <column property="customer"/>
            <column property="amount"/>
            <column property="description"/>
        </columns>
    </dataGrid>
</layout>
Java
@ViewComponent
protected JmixCheckboxGroup<String> dataGridSettingsGroup;
@ViewComponent
protected DataGrid<Order> dataGrid;

@Subscribe
protected void onInit(InitEvent event) {
    initSettingsGroup();
}

protected void initSettingsGroup() {
    dataGridSettingsGroup.setItems("Sortable", "Resizable", "Column reordering allowed");
    dataGridSettingsGroup.setTypedValue(Collections.singletonList("Sortable"));
}

@Subscribe("dataGridSettingsGroup")
protected void onDataGridSettingsGroupValueChange(
        TypedValueChangeEvent<JmixCheckboxGroup<String>, Collection<String>> event) {
    if (event.getValue() == null) {
        return;
    }

    //clear
    dataGrid.getAllColumns().forEach(col -> {
        col.setSortable(false);
        col.setResizable(false);
    });
    dataGrid.setColumnReorderingAllowed(false);

    event.getValue()
            .forEach(this::applyGridSettings);
}

protected void applyGridSettings(String setting) {
    switch (setting) {
        case "Sortable" -> dataGrid.getAllColumns().forEach(col -> col.setSortable(true));
        case "Resizable" -> dataGrid.getAllColumns().forEach(col -> col.setResizable(true));
        case "Column reordering allowed" -> dataGrid.setColumnReorderingAllowed(true);
    }
}

Data-aware dataGrid

Set dataContainer to display entities from a collection container, or use a key-value collection container for scalar and aggregate query results. The grid observes its container, so reloading or changing the container updates the rows.

Collection Container

Typically, you bind dataGrid declaratively by setting dataContainer to a collection container. The basics example uses this approach.

Key-Value Container

Bind the grid to a key-value container when the query returns scalar values or aggregates. The columns in this example correspond to the properties declared by the key-value container:

<data>
    <keyValueCollection id="salesDc">
        <loader id="salesLoader">
            <query>
                <![CDATA[select o.customer, sum(o.amount) from uisamples_Order o group by o.customer]]>
            </query>
        </loader>
        <properties>
            <property class="io.jmix.uisamples.entity.Customer"
                      name="customer"/>
            <property datatype="decimal"
                      name="sum"/>
        </properties>
    </keyValueCollection>
</data>
<facets>
    <dataLoadCoordinator auto="true"/>
</facets>
<layout>
    <dataGrid id="salesDataGrid"
              width="100%"
              minHeight="20em"
              dataContainer="salesDc">
        <columns>
            <column property="customer" header="Customer"/>
            <column property="sum" header="Sum"/>
        </columns>
    </dataGrid>
</layout>

Programmatic Binding

For a container created in Java, set metaClass instead of dataContainer so the XML columns can resolve entity metadata:

<dataGrid width="100%" id="dataGrid" metaClass="User">
    <columns>
        <column property="firstName"/>
        <column property="lastName"/>
        <column property="username"/>
        <column property="joiningDate"/>
        <column property="onboardingStatus"/>
    </columns>
</dataGrid>

Then wrap the programmatic container in ContainerDataGridItems and pass it to the grid. The wrapper propagates container changes and selection state through the grid’s data-provider API:

@ViewComponent
private DataGrid<User> dataGrid;

@ViewComponent
private CollectionContainer<User> usersDc;

@Subscribe
public void onInit(InitEvent event) {
    dataGrid.setItems(new ContainerDataGridItems<>(usersDc));
}

Programmatic Columns

addColumn() and addComponentColumn() let you create columns in Java instead of XML.

Use addColumn() with renderer instances such as the LocalDateRenderer shown in Local Date Renderer. Use addComponentColumn() when each cell should contain a UI component.

The following example creates a data grid with a user-defined number of columns:

XML
<hbox>
    <integerField id="columnCountField" placeholder="Number of columns" value="3">
        <validators>
            <max value="10"/>
            <min value="1"/>
        </validators>
    </integerField>
    <button id="createDataGridBtn" text="Create DataGrid"/>
</hbox>
<vbox id="box" padding="false"/>
Java
@ViewComponent
protected VerticalLayout box;
@ViewComponent
protected JmixIntegerField columnCountField;

@Autowired
protected DataComponents dataComponents;
@Autowired
protected UiComponents uiComponents;
@Autowired
protected Notifications notifications;

@Subscribe("createDataGridBtn")
protected void onCreateDataGridBtnClick(ClickEvent<JmixButton> event) {
    box.removeAll();
    Integer columnCount = columnCountField.getValue();

    if (columnCount == null || columnCount < 1 || columnCount > 10) {
        notifications.create("Column count must be between 1 and 10")
                .withType(Notifications.Type.WARNING)
                .withCloseable(false)
                .show();
        return;
    }

    KeyValueCollectionContainer container = createDataContainer(columnCount);
    DataGrid<KeyValueEntity> dataGrid = createDataGrid(columnCount, container);
    box.add(dataGrid);
}

protected DataGrid<KeyValueEntity> createDataGrid(Integer columnCount, KeyValueCollectionContainer container) {
    DataGrid<KeyValueEntity> dataGrid = uiComponents.create(DataGrid.class);
    dataGrid.setWidthFull();
    dataGrid.setMinHeight("12em");

    for (int col = 1; col <= columnCount; col++) {
        dataGrid.addColumn("prop" + col, container.getEntityMetaClass().getPropertyPath("prop" + col))
                .setHeader("Prop" + col);
    }
    dataGrid.setItems(new ContainerDataGridItems(container));
    return dataGrid;
}

protected KeyValueCollectionContainer createDataContainer(Integer columnCount) {
    KeyValueCollectionContainer container = dataComponents.createKeyValueCollectionContainer();

    for (int col = 1; col <= columnCount; col++) {
        container.addProperty("prop" + col, String.class);
    }

    container.setItems(loadData(columnCount));
    return container;
}

protected Collection<KeyValueEntity> loadData(Integer columnCount) {
    Collection<KeyValueEntity> list = new ArrayList<>();

    for (int row = 0; row < 5; row++) {
        KeyValueEntity entity = new KeyValueEntity();

        for (int col = 1; col <= columnCount; col++) {
            entity.setValue("prop" + col, "value" + row + col);
        }
        list.add(entity);
    }

    return list;
}

Multi-Select Mode

By default, the component operates in single‑selection mode. When you set selectionMode to MULTI, a checkbox column appears, allowing you to select any number of rows. To select every row, click the checkbox in the header row.

XML
<select id="selectionModeSelect" label="Selection mode"/>
<hbox id="buttonsPanel" width="100%" wrap="true">
    <button id="greetAllBtn" action="customersDataGrid.greetAll"/>
    <button id="greetOneBtn" action="customersDataGrid.greetOne"/>
</hbox>
<dataGrid id="customersDataGrid"
          width="100%"
          minHeight="20em"
          dataContainer="customersDc">
    <actions>
        <action id="greetAll" type="list_itemTracking" icon="COMMENTS" text="Greet all"/>
        <action id="greetOne" type="list_itemTracking" icon="COMMENT" text="Greet single selected"/>
    </actions>
    <columns>
        <column property="name"/>
        <column property="lastName"/>
        <column property="email"/>
        <column property="age"/>
        <column property="active"/>
        <column property="grade"/>
    </columns>
</dataGrid>
Java
@ViewComponent
protected JmixSelect<Grid.SelectionMode> selectionModeSelect;
@ViewComponent
protected DataGrid<Customer> customersDataGrid;
@Subscribe
protected void onInit(InitEvent event) {
    ComponentUtils.setItemsMap(selectionModeSelect, getSelectionModeItemsMap());
    selectionModeSelect.setValue(Grid.SelectionMode.NONE);
}

@Subscribe("selectionModeSelect")
protected void onSelectionModeValueChange(
        ComponentValueChangeEvent<JmixSelect<Grid.SelectionMode>, Grid.SelectionMode> event) {
    customersDataGrid.setSelectionMode(event.getValue());
}
protected Map<Grid.SelectionMode, String> getSelectionModeItemsMap() {
    return Arrays.stream(Grid.SelectionMode.values())
            .collect(Collectors.toMap(Function.identity(), mode -> mode.name().replace('_', ' ')));
}
Range selection using Shift + click or other shortcuts is not currently supported.

Inline Editing

The component supports inline editing, allowing users to switch between reading and editing table data. Inline editing can be activated through buttons in the actions column or on double click.

Inline editing updates entity attributes in memory. To save changes to the database, use the following methods:

  • In a detail view, entities are merged into the DataContext. Changes will be saved automatically when the user clicks OK, triggering DataContext.save().

  • In a list view, where DataContext is not used, save changes with DataManager as explained below.

Actions Column

Use editorActionsColumn to provide Edit and Close buttons next to each row. These enable users to start and stop editing respectively.

The actions column is added relatively to other columns. When includeAll="true" is set, the editor column is placed at the far right.
<dataGrid id="customersDataGridNonBuffered"
          width="100%"
          height="100%"
          minHeight="20em"
          dataContainer="customersDc">
    <columns>
        <column property="name" editable="true"/>
        <column property="lastName" editable="true"/>
        <column property="email"/>
        <column property="age"/>
        <column property="active"/>
        <column property="grade" editable="true"/>
        <editorActionsColumn key="nonBufferedEditorColumn">
            <editButton icon="PENCIL" text="msg:///actions.Edit"/>
            <closeButton icon="CHECK"/>
        </editorActionsColumn>
    </columns>
</dataGrid>

Buffered Mode

With buffered mode on, users will have to confirm or cancel their edits reducing the risk of accidental modifications. Clicking on a different row discards edits.

<dataGrid id="customersDataGridBuffered"
          width="100%"
          height="100%"
          minHeight="20em"
          dataContainer="customersDc"
          editorBuffered="true">
    <columns>
        <column property="name" editable="true"/>
        <column property="lastName" editable="true"/>
        <column property="email"/>
        <column property="age"/>
        <column property="active"/>
        <column property="grade" editable="true"/>
        <editorActionsColumn key="bufferedEditorColumn">
            <editButton icon="PENCIL" text="msg:///actions.Edit"/>
            <saveButton icon="CHECK" themeNames="success"/>
            <cancelButton icon="CLOSE" themeNames="error" text="msg:///actions.Cancel"/>
        </editorActionsColumn>
    </columns>
</dataGrid>

Edit on Double Click

Sometimes it is more convenient to start inline editing by double-clicking the item.

<dataGrid width="100%" dataContainer="usersDc" id="dblClickTable">
    <columns>
        <column property="username"/>
        <column property="firstName" editable="true"/>
        <column property="lastName" editable="true"/>
        <column property="active" editable="true"/>
        <column property="onboardingStatus"/>
    </columns>
</dataGrid>
@ViewComponent
private DataGrid<User> dblClickTable;

@ViewComponent
private GridMenuItem<Object> emailItem;


@Subscribe
public void onInit(InitEvent event) {
    DataGridEditor<User> tableEditor = dblClickTable.getEditor();
    dblClickTable.addItemDoubleClickListener(e -> {
        tableEditor.editItem(e.getItem());
        Component editorComponent = e.getColumn().getEditorComponent();
        if (editorComponent instanceof Focusable) {
            ((Focusable) editorComponent).focus();
        }
    });
}

Auto-Save Edits

In a typical list view, loaded entities are not merged into DataContext (if the loader XML element has the readOnly="true" attribute). Besides, list views usually do not include confirmation actions such as OK or Save. Therefore, modified entities should be saved to the database explicitly.

In non-buffered mode, you can do it by using DataManager in an EditorCloseEvent as follows:

In buffered mode, do the same in the EditorSaveEvent listener. The sample implements both variants:

@Autowired
private DataManager dataManager;
@Autowired
private Notifications notifications;

@ViewComponent
private CollectionContainer<Customer> customersDc;
@ViewComponent
private CollectionLoader<Customer> customersDl;
@ViewComponent
private JmixCheckbox immediateCheckbox;
@ViewComponent
private JmixButton saveButton;

private final Set<Customer> changedCustomers = new HashSet<>();

@Install(to = "customersDataGridBuffered.@editor", subject = "saveListener")
private void customersDataGridBufferedEditorSaveListener(final EditorSaveEvent<Customer> event) {
    saveOrEnqueue(event.getItem());
}

@Install(to = "customersDataGridNonBuffered.@editor", subject = "closeListener")
private void customersDataGridNonBufferedEditorCloseListener(final EditorCloseEvent<Customer> event) {
    saveOrEnqueue(event.getItem());
}

@Subscribe("immediateCheckbox")
public void onImmediateCheckboxComponentValueChange(final AbstractField.ComponentValueChangeEvent<JmixCheckbox, Boolean> event) {
    saveButton.setEnabled(!event.getValue());
}

@Subscribe(id = "saveButton", subject = "clickListener")
public void onSaveButtonClick(final ClickEvent<JmixButton> event) {
    saveEnqueuedChanges();
}

private void saveOrEnqueue(Customer customer) {

    if (immediateCheckbox.getValue()) {
        saveChanges(customer);
    } else {
        changedCustomers.add(customer);
    }
}

private void saveChanges(Customer customer) {
    // Save changed entity
    Customer savedCustomer = dataManager.save(customer);
    // Replace the original entity in the data container with the saved one
    customersDc.replaceItem(savedCustomer);
    // Note that if the original entity is merged into DataContext (this is the
    // case when <loader readOnly="false">), the saved instance should be merged too
    notifications.show("Changes saved to the database");
}

private void saveEnqueuedChanges() {
    if (!changedCustomers.isEmpty()) {
        // Save enqueued changes. The returned value is ignored because all data will be reloaded.
        dataManager.saveAll(changedCustomers);
        changedCustomers.clear();
        notifications.show("Changes saved to the database");
        customersDl.load();
    }
}

To generate listeners as annotated methods, use the Handlers tab of the Jmix Studio’s Component Inspector, where editor event listeners are marked with [Editor] prefixes.

The listeners can also be added programmatically via dataGrid.getEditor().addSaveListener() and similar methods.

DataGridEditor

The io.jmix.flowui.component.grid.editor.DataGridEditor interface provides additional editor functionality: configure an editor, open the editor, save and cancel a row editing, register listeners, and utility methods for defining column edit components.

To support framework mechanisms like data containers, value sources, etc., the column editor component must be added using DataGridEditor methods (DataGridEditor#setColumnEditorComponent()) instead of direct column API Column#setEditorComponent().

See the example:

@Autowired
private UiComponents uiComponents;

@ViewComponent
private DataGrid<User> editableUserTable;

@Subscribe
public void onInit(InitEvent event) {
    DataGridEditor<User> editor = editableUserTable.getEditor(); (1)

    editor.setColumnEditorComponent("timeZoneId", generationContext -> {
        //noinspection unchecked
        JmixComboBox<String> timeZoneField = uiComponents.create(JmixComboBox.class); (2)
        timeZoneField.setItems(List.of(TimeZone.getAvailableIDs()));
        timeZoneField.setValueSource(generationContext.getValueSourceProvider().getValueSource("timeZoneId"));
        timeZoneField.setWidthFull();
        timeZoneField.setClearButtonVisible(true);
        timeZoneField.setRequired(true);
        //noinspection unchecked,rawtypes
        timeZoneField.setStatusChangeHandler(((Consumer) generationContext.getStatusHandler())); (3)

        return timeZoneField; (4)
    });
}
1 Get the instance of DataGridEditor.
2 The JmixComboBox component instance is created using the UiComponents factory.
3 Set StatusChangeHandler.
4 The setColumnEditorComponent() method returns the visual component to be shown as the column editor component.

SupportsStatusChangeHandler

By default, field components (for example, textField, comboBox) display error messages in a label above them. Such behaviour has disadvantages in case of limited area of edit cell. The io.jmix.flowui.component.SupportsStatusChangeHandler interface enables to define different way of displaying error messages. Components that implement this interface support error handling delegation.

By default, the inline editor uses StatusChangeHandler to set error message of a component as its title.

The component can include header and footer sections to show supplementary information. Each section can contain one or more rows, which you can add using the following methods:

Method

Description

appendHeaderRow()

Adds a new row at the bottom of the header section.

prependHeaderRow()

Adds a new row at the top of the header section.

appendFooterRow()

Adds a new row at the bottom of the footer section.

prependFooterRow()

Adds a new row at the top of the footer section.

The following example demonstrates a dataGrid that includes merged cells in its header and a computed value in the footer:

XML
<dataGrid id="dataGrid"
          width="100%"
          minHeight="20em"
          dataContainer="countryGrowthDc"
          themeNames="column-borders row-stripes">
    <columns>
        <column property="country"/>
    </columns>
</dataGrid>
Java
@ViewComponent
protected DataGrid<CountryGrowth> dataGrid;

@Autowired
protected MessageTools messageTools;
@Autowired
protected Metadata metadata;

protected DecimalFormat percentFormat;

@Subscribe
protected void onInit(InitEvent event) {
    initPercentFormat();
    initColumns();
    initHeader();
}

@Subscribe
protected void onBeforeShow(BeforeShowEvent event) {
    //because data is loaded
    initFooter();
}

protected void initPercentFormat() {
    percentFormat = (DecimalFormat) NumberFormat.getPercentInstance(UI.getCurrent().getLocale());
    percentFormat.setMultiplier(1);
    percentFormat.setMaximumFractionDigits(2);
}

protected void initColumns() {
    MetaClass metaClass = metadata.getClass(CountryGrowth.class);
    dataGrid.addComponentColumn(countryGrowth -> new Text(percentFormat.format(countryGrowth.getPrevYear())))
            .setHeader(messageTools.getPropertyCaption(metaClass, "prevYear"))
            .setKey("prevYear");
    dataGrid.addComponentColumn(countryGrowth -> new Text(percentFormat.format(countryGrowth.getCurrYear())))
            .setHeader(messageTools.getPropertyCaption(metaClass, "currYear"))
            .setKey("currYear");
}

protected void initHeader() {
    HeaderRow headerRow = dataGrid.prependHeaderRow();
    HeaderRow.HeaderCell headerCell = headerRow.join(
            dataGrid.getColumnByKey("prevYear"),
            dataGrid.getColumnByKey("currYear")
    );

    Span gdpGrowth = new Span("GDP growth");
    HorizontalLayout layout = new HorizontalLayout(gdpGrowth);

    layout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
    headerCell.setComponent(layout);
}

protected void initFooter() {
    FooterRow footerRow = dataGrid.appendFooterRow();

    FooterRow.FooterCell countryCell = footerRow.getCell(dataGrid.getColumnByKey("country"));
    Html html = new Html("<strong> Average: </strong>");
    countryCell.setComponent(html);

    footerRow.getCell(dataGrid.getColumnByKey("prevYear"))
            .setText(percentFormat.format(getAverage("prevYear")));
    footerRow.getCell(dataGrid.getColumnByKey("currYear"))
            .setText(percentFormat.format(getAverage("currYear")));

}

protected double getAverage(String propertyId) {
    double average = 0.0;

    Collection<CountryGrowth> items = dataGrid.getGenericDataView().getItems().toList();
    for (CountryGrowth countryGrowth : items) {
        Double value = propertyId.equals("prevYear")
                ? countryGrowth.getPrevYear()
                : countryGrowth.getCurrYear();

        average += value != null ? value : 0.0;
    }
    return average / items.size();
}

Column Headers Filtering

Data in dataGrid can be filtered using property filters embedded into column headers.

You can define which columns should have a filter using the filterable XML attribute. Filterable columns have the "funnel" icon (funnel) in their headers. If the user clicks this icon, a dialog with the property filter condition appears. If a condition is set, the icon in that column is highlighted.

To make sure the filter icon is always visible, set an appropriate width for the column using the width or autoWidth attribute. Don’t make the column resizable, otherwise users will be able to shrink the column width and lose the filter icon.

For example:

<dataGrid id="customersDataGrid"
          width="100%"
          minHeight="20em"
          dataContainer="customersDc">
    <columns>
        <column property="name" filterable="true"/>
        <column property="lastName"/>
        <column property="email"/>
        <column property="age" filterable="true"/>
        <column property="active" filterable="true"/>
        <column property="grade" filterable="true"/>
    </columns>
</dataGrid>

Property filters in column headers work in the same way as standalone property filters and genericFilter - they add conditions to the data loader. In the standard flow, the conditions are translated to the JPQL query and filter data on the database level.

Filterable columns can be used together with propertyFilter and genericFilter components. Conditions of all filters are combined by logical AND.

Currently, column filter conditions are not bound to the page URL. It means that if a user applies a filter and then navigates to a detail view and back, the filter will be cleared.

Actions

The dataGrid component implements the HasActions interface and can contain both standard list actions and custom actions. Actions are invoked by clicking designated buttons or from the context menu that appears on the right click.

To add action in Jmix Studio, select the component in the view descriptor XML or in the Jmix UI structure panel and click on the Add→Action button in the Jmix UI inspector panel.

XML
<hbox id="buttonsPanel" width="100%" wrap="true">
    <button id="createBtn" action="customersDataGrid.create"/>
    <button id="editBtn" action="customersDataGrid.edit"/>
    <button id="removeBtn" action="customersDataGrid.remove"/>
    <button id="greetingBtn" action="customersDataGrid.greeting"/>
</hbox>
<dataGrid id="customersDataGrid"
          width="100%"
          minHeight="20em"
          dataContainer="customersDc">
    <actions>
        <action id="create" type="list_create">
            <properties>
                <property name="openMode" value="DIALOG"/>
            </properties>
        </action>
        <action id="edit" type="list_edit">
            <properties>
                <property name="openMode" value="DIALOG"/>
            </properties>
        </action>
        <action id="remove" type="list_remove"/>
        <action id="greeting" type="list_itemTracking" icon="COMMENT" text="Greeting"/>
    </actions>
    <columns>
        <column property="name"/>
        <column property="lastName"/>
        <column property="email"/>
        <column property="age"/>
        <column property="active"/>
        <column property="grade"/>
    </columns>
</dataGrid>
Java
@ViewComponent
protected DataGrid<Customer> customersDataGrid;

@Autowired
protected Notifications notifications;
@Autowired
protected MetadataTools metadataTools;

@Subscribe("customersDataGrid.greeting")
protected void onCustomersDataGridGreetingActionPerformed(ActionPerformedEvent event) {
    Customer customer = customersDataGrid.getSingleSelectedItem();

    notifications.show(customer != null
                    ? "Hello, " + metadataTools.getInstanceName(customer)
                    : "No selection");
}

Context Menu

The context menu offers an alternative way to access actions through a right click. Each action is represented by its own menu item.

Use the contextMenu element to refine the list of items in the menu, organizing them with separators and a hierarchical structure.

XML
<hbox id="buttonsPanel" width="100%" wrap="true">
    <button id="createBtn" action="customersDataGrid.create"/>
    <button id="editBtn" action="customersDataGrid.edit"/>
    <button id="removeBtn" action="customersDataGrid.remove"/>
    <button id="greetingBtn" action="customersDataGrid.greeting"/>
</hbox>
<dataGrid id="customersDataGrid"
          width="100%"
          minHeight="20em"
          dataContainer="customersDc">
    <contextMenu id="contextMenu">
        <item text="CRUD" icon="TABLE">
            <item action="customersDataGrid.create" icon="PLUS"/>
            <item action="customersDataGrid.edit" icon="PENCIL"/>
            <item action="customersDataGrid.remove" icon="TRASH"/>
        </item>
        <item action="customersDataGrid.greeting" icon="COMMENT"/>
        <separator/>
        <item id="customerItem"/>
        <item action="customersDataGrid.getInfo" icon="INFO_CIRCLE_O"/>
    </contextMenu>
    <actions showInContextMenuEnabled="false">
        <action id="create" type="list_create">
            <properties>
                <property name="openMode" value="DIALOG"/>
            </properties>
        </action>
        <action id="edit" type="list_edit">
            <properties>
                <property name="openMode" value="DIALOG"/>
            </properties>
        </action>
        <action id="remove" type="list_remove"/>
        <action id="greeting" type="list_itemTracking" icon="COMMENT" text="Greeting"/>
        <action id="getInfo" type="list_itemTracking" icon="INFO_CIRCLE_O" text="Get info"/>
    </actions>
    <columns>
        <column property="name"/>
        <column property="lastName"/>
        <column property="email"/>
        <column property="age"/>
        <column property="active"/>
        <column property="grade"/>
    </columns>
</dataGrid>
Java
@ViewComponent
protected DataGrid<Customer> customersDataGrid;

@Autowired
protected Notifications notifications;
@Autowired
protected Dialogs dialogs;
@Autowired
protected MetadataTools metadataTools;

@ViewComponent
protected GridMenuItem<Customer> customerItem;

@Install(to = "contextMenu", subject = "dynamicContentHandler")
public boolean contextMenuDynamicContentHandler(Customer customer) {
    if (customer == null) {
        return false;
    }

    customerItem.setText(metadataTools.getInstanceName(customer));
    return true;
}

@Subscribe("customersDataGrid.greeting")
public void onCustomersDataGridGreetingActionPerformed(ActionPerformedEvent event) {
    Customer customer = customersDataGrid.getSingleSelectedItem();

    notifications.show(customer != null
            ? "Hello, " + metadataTools.getInstanceName(customer)
            : "No selection");
}

@Subscribe("customersDataGrid.getInfo")
public void onCustomersDataGridGetInfoActionPerformed(ActionPerformedEvent event) {
    Customer customer = customersDataGrid.getSingleSelectedItem();

    dialogs.createMessageDialog()
            .withContent(
                    customer != null
                            ? getMessageDialogContent(customer)
                            : new Span("No selection")
            )
            .open();
}

protected Component getMessageDialogContent(Customer customer) {
    return new Html("""
            <div>
                <strong>Name:</strong> %s<br/>
                <strong>Age:</strong> %s<br/>
                <strong>Email:</strong> %s
            </div>
            """.formatted(metadataTools.getInstanceName(customer), customer.getAge(), customer.getEmail()));
}

Renderers

Renderers customize how column cells are displayed. This section covers renderer options specific to dataGrid columns. Shared renderer APIs such as ComponentRenderer, LitRenderer, and FragmentRenderer are described in Renderers.

Renderers can be defined in the following ways:

  • Declaratively in XML: use predefined renderers such as numberRenderer, localDateRenderer, localDateTimeRenderer, detailLinkRenderer, and detailButtonRenderer inside a column element.

  • Using @Supply annotation: provide a custom renderer from the view controller.

  • Using fragmentRenderer: use the shared fragment-based renderer mechanism described in Fragment Renderer.

  • Using addColumn() and addComponentColumn(): create columns in Java and configure renderers programmatically. See Programmatic Columns.

To add a renderer in Jmix Studio, select the column element in the view descriptor XML or in the Jmix UI structure panel and click Add → Renderer.

Local Date Renderer

Renders LocalDate values in a column.

<column property="joiningDate">
    <localDateRenderer format="MMM dd, yyyy"/>
</column>

Use the required format attribute to define the output format. Use the optional nullRepresentation attribute to specify how null values are displayed.

For Java configuration, use LocalDateRenderer with addColumn():

@ViewComponent
private DataGrid<User> usersDtGr;

@Subscribe
public void onInit(InitEvent event) {
    usersDtGr.addColumn(new LocalDateRenderer<>(
                    User::getJoiningDate,
                    () -> DateTimeFormatter.ofLocalizedDate(
                            FormatStyle.MEDIUM)))
            .setHeader("Joining date");
}

Local Date Time Renderer

Renders LocalDateTime values in a column.

<column property="passwordExpiration">
    <localDateTimeRenderer format="dd/MM/YYYY HH:mm:ss"/>
</column>

Use the required format attribute to define the output format. Use the optional nullRepresentation attribute to specify how null values are displayed.

Number Renderer

Renders numeric values in a column.

<column property="factor">
    <numberRenderer numberFormat="#,#00.0000"/>
</column>

Specify exactly one of format or numberFormat. If neither is specified, or if both are specified together, the framework throws a GuiDevelopmentException. Use the optional nullRepresentation attribute to specify how null values are displayed. numberFormat uses java.text.DecimalFormat syntax.

Renders a link that opens the detail view of the current entity.

<dataGrid width="100%" dataContainer="usersDc">
    <columns>
        <column property="username">
            <detailLinkRenderer viewId="User.detail"
                                target="BLANK"
                                classNames="detail-link"
                                css="font-weight: 600;"/>
        </column>
        <column property="firstName"/>
        <column property="lastName"/>
    </columns>
</dataGrid>

Supported attributes:

  • viewClass, viewId specify the target view. The renderer resolves the view in the following order: viewClass, viewId, default detail view.

  • text sets the link text. If omitted, the renderer uses the formatted value of the bound column property.

  • target sets the link target.

  • classNames, css apply styles.

The target view must have a registered route with exactly one route parameter, and the entity instance must already have a non-null id. For routes with zero or multiple route parameters, configure the renderer from Java code and provide a custom URL using DetailLinkRenderer.withHrefProvider().

Detail Button Renderer

Renders a button that opens the detail view of the current entity.

<dataGrid width="100%" dataContainer="stepsDc">
    <columns>
        <column property="name">
            <detailButtonRenderer viewId="Step.detail"
                                  icon="vaadin:edit"
                                  openMode="DIALOG"
                                  themeNames="tertiary-inline small"
                                  classNames="detail-button"
                                  css="color: var(--lumo-primary-text-color);"/>
        </column>
        <column property="duration"/>
        <column property="sortValue"/>
    </columns>
</dataGrid>

Supported attributes:

  • viewClass, viewId specify the target view. The renderer resolves the view in the following order: viewClass, viewId, default detail view.

  • openMode controls how the view is opened: NAVIGATION (default) or DIALOG.

  • text sets the button text. If omitted, the renderer uses the formatted value of the bound column property. Set it explicitly for key-only columns.

  • icon sets the button icon. You can define it through the attribute or a nested icon element.

  • themeNames, classNames, css apply styles.

Detail Renderers in Key-Only Columns

You can use detail renderers in a column with key instead of the property attribute:

<dataGrid width="100%" dataContainer="usersDc">
    <columns>
        <column property="username"/>
        <column key="openUser" header="Open">
            <detailButtonRenderer viewId="User.detail"
                                  text="Edit"
                                  openMode="DIALOG"/>
        </column>
    </columns>
</dataGrid>

In this case, set the link or button text explicitly, because there is no bound property value.

Text Renderer

Renders plain text provided from Java code.

<column key="status" header="Status"/>
@Supply(to = "userStepsDataGrid.status", subject = "renderer")
private Renderer<UserStep> userStepsDataGridStatusRenderer() {
    return new TextRenderer<>(userStep ->
            isOverdue(userStep) ? "Overdue!" : "");
}

This renderer is typically supplied from Java code by using the renderer handler or @Supply.

Component Renderer

Renders a custom component in each cell. Use this renderer when a cell must contain rich or interactive content, such as a checkbox, image, or button.

See Component Renderer for available constructors, general behavior, and performance considerations.

The following example renders a read-only checkbox in the active column and a badge in the grade column.

XML
<dataGrid id="customersDataGrid"
          width="100%"
          minHeight="20em"
          dataContainer="customersDc">
    <columns>
        <column property="name"/>
        <column property="lastName"/>
        <column property="email"/>
        <column property="age"/>
        <column property="active"/>
        <column property="grade"/>
    </columns>
</dataGrid>
Java
@Autowired
protected UiComponents uiComponents;
@Autowired
protected Messages messages;

@Supply(to = "customersDataGrid.active", subject = "renderer")
protected Renderer<Customer> activeComponentRenderer() {
    return new ComponentRenderer<>(
            () -> {
                JmixCheckbox checkbox = uiComponents.create(JmixCheckbox.class);
                checkbox.setReadOnly(true);
                return checkbox;
            },
            (checkbox, customer) -> checkbox.setValue(customer.isActive())
    );
}

@Supply(to = "customersDataGrid.grade", subject = "renderer")
protected Renderer<Customer> statusComponentRenderer() {
    return new ComponentRenderer<>(this::createGradeComponent, this::gradeComponentUpdater);
}

protected Span createGradeComponent() {
    Span span = uiComponents.create(Span.class);
    span.getElement().getThemeList().add("badge");

    return span;
}

protected void gradeComponentUpdater(Span span, Customer customer) {
    if (customer.getGrade() != null) {
        span.setText(messages.getMessage(CustomerGrade.class, customer.getGrade().name()));

        switch (customer.getGrade()) {
            case STANDARD -> span.getElement().getThemeList().add("contrast");
            case HIGH -> span.getElement().getThemeList().add("success");
            case PREMIUM -> span.getElement().getThemeList().add("primary");
        }
    } else {
        span.setText("No data");
    }
}

Item Details

This component allows rows to expand, revealing additional information about each item. To implement this functionality, use the setItemDetailsRenderer() method. This method takes a ComponentRenderer as an argument, which specifies how the details are rendered.

The detailsVisibleOnClick attribute allows you to control whether these details appear when the row is clicked. If you prefer the details to be triggered by an event other than clicking, set this attribute to false.

XML
<dataGrid id="customersDataGrid"
          width="100%"
          minHeight="20em"
          dataContainer="customersDc">
    <columns>
        <column property="name"/>
        <column property="lastName"/>
    </columns>
</dataGrid>
Java
@ViewComponent
protected DataGrid<Customer> customersDataGrid;

@Autowired
protected CustomerDetailsGenerator detailsGenerator;

@Subscribe
protected void onInit(InitEvent event) {
    detailsGenerator.setReadOnlyMode(true);
    customersDataGrid.setItemDetailsRenderer(createCustomerDetailsRenderer());
}

protected ComponentRenderer<FormLayout, Customer> createCustomerDetailsRenderer() {
    return new ComponentRenderer<>(detailsGenerator::createCustomerDetailsRenderer, detailsGenerator::setCustomer);
}

Sorting

This section demonstrates sorting customization for individual dataGrid component. For application-wide sorting customization, see Sorting.

Any column can be used for sorting the data displayed. Clicking on the column header activates a special control in the grid that shows which column is currently being sorted and the sorting direction (ascending or descending). Each subsequent click on the same header will toggle the sorting direction.

Enumeration attributes are sorted by their id values, not by localized captions displayed in the UI.

As a result, the sorting order can differ from the visible order of enum values in a dataGrid, especially when enum values are localized.

Sorting by Multiple Columns

It is possible to sort by multiple columns simultaneously. When sorting by multiple columns, the first selected column determines the primary sorting criterion, the second column sets the secondary criterion, and so on. You can configure it using multiSort, multiSortOnShiftClickOnly, and multiSortPriority attributes.

For example:

<dataGrid id="customersDataGrid"
          width="100%"
          minHeight="20em"
          dataContainer="customersDc"
          multiSort="true"
          multiSortPriority="APPEND">
    <columns>
        <column property="name"/>
        <column property="lastName"/>
        <column property="email"/>
        <column property="age"/>
        <column property="active"/>
        <column property="grade"/>
    </columns>
</dataGrid>

Column Comparator

Use DataGridColumn#setComparator() to define custom sorting logic for a specific column when sorting is performed in memory. In-memory sorting is used when the container has no loader, or when only the first page is loaded and the number of loaded items is less than the page size.

The following example sorts the Department.num attribute as numbers even though the attribute type is String:

<dataGrid id="inMemoryDepartmentsDataGrid"
          width="100%"
          dataContainer="inMemoryDepartmentsDc">
    <columns>
        <column property="name"/>
        <column property="num"/>
    </columns>
</dataGrid>
private void initInMemoryComparator() {
    inMemoryDepartmentsDc.setItems(dataManager.load(Department.class)
            .query("select e from Department e")
            .list());

    inMemoryDepartmentsDataGrid.getColumnByKey("num")
            .setComparator(Comparator.comparing(
                    department -> department.getNum() == null ? null : Integer.valueOf(department.getNum()),
                    Comparator.nullsFirst(Integer::compareTo)));
}

Sort Builder Delegate

If the grid reloads data from the database, configure Sort Builder Delegate. The delegate receives sorting instructions from the grid and returns a DataGridSort object, allowing you to override in-memory sorting, database sorting, or both in one place.

The following example sorts the customer column by customer name and last name:

XML
<simplePagination id="pagination"
                  dataLoader="ordersDl"
                  itemsPerPageDefaultValue="4"/>
<dataGrid id="ordersDataGrid"
          width="100%"
          minHeight="20em"
          dataContainer="ordersDc">
    <columns>
        <column property="date"/>
        <column property="customer"/>
        <column property="amount"/>
    </columns>
</dataGrid>
Java
@Install(to = "ordersDataGrid", subject = "sortBuilderDelegate")
public DataGridSort ordersDataGridSortBuilderDelegate(DataGridSortContext<Order> context) {
    return DataGridSortBuilder.create(context)
            .replaceSort("customer", List.of("{E}.customer.name", "{E}.customer.lastName"))
            .build();
}

Sorting a Computed Column

Sort Builder Delegate is also useful for columns that are not bound to an entity property. In the example below, the fullName column is rendered from firstName and lastName, and the sort builder delegate maps this synthetic column to both JPQL expressions and an in-memory comparator:

<dataGrid id="fullNameUsersDataGrid"
          width="100%"
          dataContainer="usersDc">
    <columns>
        <column key="fullName" header="Full name" sortable="true"/>
        <column property="username"/>
        <column property="department.name"/>
        <column property="onboardingStatus"/>
    </columns>
</dataGrid>
private void initComputedColumnSortBuilder() {
    fullNameUsersDataGrid.setSortBuilderDelegate(sortContext ->
            DataGridSortBuilder.create(sortContext)
                    .replaceSort("fullName",
                            List.of("{E}.firstName", "{E}.lastName"),
                            Comparator.comparing(User::getFirstName,
                                            Comparator.nullsFirst(String::compareTo))
                                    .thenComparing(User::getLastName,
                                            Comparator.nullsFirst(String::compareTo)))
                    .build());
}
@Supply(to = "fullNameUsersDataGrid.fullName", subject = "renderer")
protected Renderer<User> fullNameUsersDataGridFullNameRenderer() {
    return new TextRenderer<>(user ->
            ((user.getFirstName() == null ? "" : user.getFirstName()) + " "
                    + (user.getLastName() == null ? "" : user.getLastName())).trim());
}

Aggregating

The component supports aggregating values in its rows. When aggregation is enabled, it will display an additional row containing aggregated values. For example:

<dataGrid id="ordersDataGrid"
          width="100%"
          minHeight="20em"
          dataContainer="ordersDc"
          aggregatable="true"
          aggregationPosition="TOP">
    <columns>
        <column property="date"/>
        <column property="customer"/>
        <column property="customer.grade">
            <aggregation
                    strategyClass="io.jmix.uisamples.view.flowui.components.datagrid.aggregation.DataGridCustomerGradeAggregation"/>
        </column>
        <column property="amount">
            <aggregation type="SUM"/>
        </column>
        <column property="description"/>
    </columns>
</dataGrid>

The aggregation element can specify a class with custom aggregation logic through strategyClass attribute:

Such class must implement AggregationStrategy interface. For example:

@Autowired
public Messages messages;

@Override
public String aggregate(Collection<CustomerGrade> propertyValues) {
    CustomerGrade mostFrequent = null;
    long max = 0;

    if (CollectionUtils.isNotEmpty(propertyValues)) {
        for (CustomerGrade grade : CustomerGrade.values()) {
            long current = propertyValues.stream()
                    .filter(grade::equals)
                    .count();

            if (current > max) {
                mostFrequent = grade;
                max = current;
            }
        }
    }

    if (mostFrequent != null) {
        String key = CustomerGrade.class.getSimpleName() + "." + mostFrequent.name();
        return String.format("%s: %d/%d", messages.getMessage(CustomerGrade.class, key), max, propertyValues.size());
    }

    return "NaN";
}

@Override
public Class<String> getResultClass() {
    return String.class;
}

Empty State

When a data grid has no data to display, the area between the header and footer is blank by default. Use the empty state feature to display a message or UI component informing the user that there are no items to show.

  • Use the emptyStateText attribute to define the text that appears when the data grid is empty.

    <dataGrid id="dataGridEmptyState"
              dataContainer="customersDc"
              width="100%"
              emptyStateText="No customers found">
        <columns>
            <column property="firstName"/>
            <column property="lastName"/>
            <column property="age"/>
            <column property="martialStatus"/>
        </columns>
    </dataGrid>
    This attribute takes precedence over any empty state component configured using the setEmptyStateComponent(Component) method in the view controller or the emptyStateComponent element in the view XML-descriptor. Setting emptyStateText will remove any existing component.
  • Use the emptyStateComponent element to define the component that appears when the grid is empty.

    <hbox id="buttonsPanel" width="100%" wrap="true">
        <button id="unloadBtn" action="customersDataGrid.unload"/>
    </hbox>
    <dataGrid id="customersDataGrid"
              width="100%"
              minHeight="20em"
              dataContainer="customersDc">
        <actions>
            <action id="unload" icon="ERASER" text="msg://customersDataGrid.unload.text"
                    enabled="false"/>
        </actions>
        <columns>
            <column property="name"/>
            <column property="lastName"/>
            <column property="email"/>
            <column property="age"/>
            <column property="active"/>
            <column property="grade"/>
        </columns>
        <emptyStateComponent>
            <vbox padding="false" height="100%"
                  justifyContent="CENTER" alignItems="CENTER">
                <h3 text="msg://emptyState.text"/>
                <button id="loadBtn" text="msg://loadBtn.text" icon="REFRESH" themeNames="primary"/>
            </vbox>
        </emptyStateComponent>
    </dataGrid>
    @ViewComponent
    private CollectionContainer<Customer> customersDc;
    @ViewComponent
    private CollectionLoader<Customer> customersDl;
    
    @ViewComponent("customersDataGrid.unload")
    private Action unloadAction;
    
    @Subscribe("customersDataGrid.unload")
    public void onCustomersDataGridUnloadActionPerformed(ActionPerformedEvent event) {
        customersDc.getMutableItems().clear();
        unloadAction.setEnabled(false);
    }
    
    @Subscribe("loadBtn")
    public void onLoadBtnClick(ClickEvent<JmixButton> event) {
        customersDl.load();
        unloadAction.setEnabled(true);
    }

    Once you’ve set emptyStateComponent, you can interact with the nested component as usual. This includes customizing its styling and creating event handlers.

    Setting this element overrides any empty state text defined using the setEmptyStateText(String) method in the view controller or the emptyStateText attribute in the view XML-descriptor.

Theme Variants

Use themeNames attribute to set a component theme.

Variant Description Supported By

column-borders

Adds vertical borders between columns.

Aura, Lumo

compact

Reduces row height and spacing.

Lumo

no-border

Removes the outer border around the grid.

Aura, Lumo

no-row-borders

Removes the horizontal borders between rows.

Aura, Lumo

row-stripes

Alternates row background colors to improve readability.

Aura, Lumo

wrap-cell-content

Allows cell content to wrap onto multiple lines instead of truncating.

Aura, Lumo

Attributes

The following attributes are specific to dataGrid:

Name Description Default

allRowsVisible

If true, the data grid will display all rows at once with no scroll bars, effectively disabling virtual scrolling. This means that instead of only rendering the visible rows and loading more as the user scrolls, the grid will render all the rows in the DOM simultaneously.

Using this feature is discouraged for a large number of items as it may cause performance issues.

false

aggregatable

If true, enables aggregating of columns. See Aggregating.

false

aggregationPosition

If the data in the component is aggregatable, determines whether the aggregation row is displayed above or below the other rows. Possible values: TOP or BOTTOM. See Aggregating.

BOTTOM

columnReorderingAllowed

If true, enables users to change the order of columns.

false

detailsVisibleOnClick

If true, enables item details to be revealed on mouse click.

true

dropMode

Determines rows where a drop can happen. Possible values: BETWEEN, ON_TOP, ON_TOP_OR_BETWEEN, ON_GRID. This feature might be used, for example, to reorder rows and to drag rows between grids.

editorBuffered

If true, activates buffered mode for inline editing, meaning that the user must confirm making changes by clicking a confirm button. This mode also allows users to cancel their changes. In unbuffered mode changes are applied without the need for confirmation.

false

emptyStateText

The text to display when the data grid is empty. Use null to remove the current empty state content. See Empty State.

multiSort

If true, enables sorting by multiple columns.

false

multiSortOnShiftClickOnly

If true, multi-sorting is activated only when clicking on the column header while holding down the Shift key.

false

multiSortPriority

Determines whether the clicked column is added to the end or beginning of the sorted columns list. Possible values: PREPEND, APPEND.

PREPEND

nestedNullBehavior

Sets the behavior when parsing nested properties which may contain null values in the property chain. Possible values: THROW, ALLOW_NULLS.

THROW

pageSize

Determines the page size or the number of items that will be fetched from the data provider at a time.

50

rowsDraggable

If true, enables users to drag rows in the grid.

false

selectionMode

Sets the selection mode. Possible values: SINGLE, MULTI, NONE.

SINGLE

Elements

Elements of dataGrid provide a wide range of options to control the appearance, behavior, and functionality of columns both collectively and individually.

To add an element to a selected component click the Add button in the Jmix UI inspector panel.

columns

The columns element can specify a set of attributes to display and behaviors for all columns.

XML Element

columns

Attributes

exclude - includeAll - resizable - sortable

Elements

column - EditorActionsColumn

Table 1. Attributes

Name

Description

Default

exclude

Excludes specific attributes from being shown. Several attributes must be separated with a comma. For example: exclude = "id, version, sortValue".

includeAll

If true includes all the attributes specified in the corresponding data container’s fetch plan.

resizable

If true, all columns are user-resizable.

false

sortable

If true, all columns are sortable.

true

column

The column element defines an individual column. Attributes set for an individual column override those set for all columns.

XML Element

column

Attributes

autowidth - editable - filterable - flexGrow - footer - frozen - header - key - property - resizable - sortable - textAlign - visible - width

Handlers

AttachEvent - DataGridColumnVisibilityChangedEvent - DetachEvent - partNameGenerator - renderer - tooltipGenerator

Elements

Aggregation - FragmentRenderer - LocalDateRenderer - LocalDateTimeRenderer - NumberRenderer - DetailLinkRenderer - DetailButtonRenderer

Table 2. Attributes

Name

Description

Default

autoWidth

If true, the column width will adjust to its contents.

false

editable

If true, the column can be edited. See Inline Editing.

false

filterable

If true enables filtering for this column. See Filtering.

false

flexGrow

Sets the flex grow ratio for this column. When set to 0, the column width is fixed.

0

footer

Sets a footer text to the column. The attribute value can either be the text itself or a key in the message bundle. In case of a key, the value should begin with the msg:// prefix.

frozen

If true, freezes (locks in place) the column, so that it remains visible as the user scrolls the table horizontally. It’s generally recommended to freeze columns from left to right.

false

header

Sets the column header text. The attribute value can either be the text itself or a key in the message bundle. In case of a key, the value should begin with the msg:// prefix.

key

Sets the user-defined identifier to map this column. The key can be used to fetch the column later with the getColumnByKey(String) method.

The key has to be unique within the data grid, and it can’t be changed after set once.

property

Specifies the name of an entity attribute to be displayed in the column. This can be an attribute of the root entity property = "user" or an attribute of its child entity property = `user.department.name (use dot notation to traverse the object graph).

resizable

If true, the column is user-resizable.

false

sortable

If true, the column is sortable.

false

textAlign

Specifies the alignment of the text with the following possible values: START, CENTER, END. See Text Alignment.

START

visible

If true, the column is visible.

true

width

Sets the width of the column as a CSS string.

Table 3. Handlers

Name

Description

DataGridColumnVisibilityChangedEvent

Fired when the column visibility is changed through the gridColumnVisibility component.

partNameGenerator

Generates parts of CSS class names for this column based on given conditions. This allows for customizing cell appearance based on the data displayed. See live demo.

renderer

Renders column content using text or components. See text renderer and component renderer.

tooltipGenerator

Generates tooltip for the column cell based on given conditions. See live demo.

contextMenu

The contextMenu element organizes items the right-click menu in a way that differs from their default arrangement. See the example.

XML Element

contextMenu

Attributes

id - classNames - css - enabled - visible

Handlers

AttachEvent - DetachEvent - GridContextMenuOpenedEvent - openedChangeEvent - dynamicContentHandler

Elements

item - separator

Table 4. Handlers

Name

Description

GridContextMenuOpenedEvent

Fired when the context menu opened state changes. May return the target item or the id of the target column allowing the menu to display items based on the clicked item.

openedChangeEvent

Fired when the context menu opened state changes.

dynamicContentHandler

Handles dynamic updates to the menu when it opens, such as adding menu items or their content. See the example.

emptyStateComponent

The emptyStateComponent element defines the component to display when the grid is empty. Use null to remove the current empty state content. See Empty State.