v7.3.0
SupportExamplesFree Trial

RowExpander
Feature

Enables expanding of Grid rows by either row click or double click, or by adding a separate Grid column which renders a button that expands or collapses the row.

const grid = new Grid({ appendTo : targetElement, height : 320, features : { // Enable the feature rowExpander : { columnPosition : 'last', renderer({ record }) { return `<div><div>Introduction in Latin</div><div>${record.notes} ${record.notes}</div></div>`; } } }, data : DataGenerator.generateData(15), columns : [ { field : 'firstName', text : 'First name', flex : 1 }, { field : 'surName', text : 'Surname', flex : 1 }, { field : 'age', text : 'Age', flex : 1 } ] }); grid.features.rowExpander.expand(grid.store.first);

The content of the expanded row body is rendered by providing either a renderer function to the rowExpander feature config:

new Grid({
   features : {
       rowExpander : {
           renderer({record, region, expanderElement}){
               return htmlToBeExpanded;
           }
       }
   }
});

Or a widget configuration object:

new Grid({
   features : {
       rowExpander : {
           widget : {
               type : 'detailGrid',
           },
           dataField : 'orderDetails'
       }
   }
});

class LineItem extends GridRowModel { static fields = [ 'name', 'quantity', { name : 'price', type : 'number' } ]; // Computed field that sums up the lines total price get total() { return this.price * this.quantity; } } class Order extends GridRowModel { static fields = [ { name : 'date', type : 'date' }, // This is the field from which the expanded LineItemGrid will get its data // The type "store" means that this field has a number of records in itself { name : 'details', type : 'store', storeClass : Store, modelClass : LineItem } ]; // Computed field that sums up the order total get total() { return this.details?.sum('total') || 0; } } const detailsGridConfig = { type : 'grid', // The Grid will adjust its height to fit all rows autoHeight : true, selectionMode : { // Adds a checkbox column that lets the user select rows checkbox : true, // Adds a checkbox to the checkbox column header that lets the user check/uncheck all rows showCheckAll : true }, columns : [ { // A template column that renders an icon next to the product name type : 'template', text : 'Product', field : 'name', flex : 3, template : ({ record }) => `<i class="${record.icon} fa-fw"></i>${record.name}` }, { // A widget column that lets the user increase and decrease the quantity value type : 'number', text : 'Quantity', field : 'quantity', width : 150 }, { text : 'Price', type : 'number', field : 'price', width : 100, format : { style : 'currency', currency : 'USD' } }, { type : 'action', width : 40, actions : [{ cls : 'fa fa-trash', tooltip : 'Delete item', onClick : async({ record }) => { if (await MessageDialog.confirm({ title : 'Please confirm', message : 'Delete this line item?' }) === MessageDialog.yesButton) { record.remove(); } } }] } ] }; const grid = new Grid({ appendTo : targetElement, height : 400, store : { modelClass : Order }, features : { // Enable the feature rowExpander : { dataField : 'details', widget : detailsGridConfig } }, columns : [ { field : 'id', text : 'Order', flex : 1 }, { field : 'date', text : 'Date', type : 'date', flex : 1 }, { type : 'number', field : 'total', text : 'Total', align : 'right', width : 100, format : { style : 'currency', currency : 'USD' } } ], data : [ { id : '123456', date : '2022-12-10', details : [ { id : 111, name : 'Milk', icon : 'fa fa-cow', quantity : 2, price : 1.99 }, { id : 112, name : 'Bread', icon : 'fa fa-bread-slice', quantity : 1, price : 2.49 }, { id : 113, name : 'Eggs', icon : 'fa fa-egg', quantity : 4, price : 0.99 }, { id : 114, name : 'Apples', icon : 'fa fa-apple-whole', quantity : 3, price : 0.69 } ] }, { id : '4368943', date : '2022-12-22', details : [ { id : 122, name : 'Rice', icon : 'fa fa-bowl-rice', quantity : 2, price : 3.49 }, { id : 124, name : 'Lemons', icon : 'fa fa-lemon', quantity : 1, price : 0.69 }, { id : 121, name : 'Peppers', icon : 'fa fa-pepper-hot', quantity : 4, price : 2.99 }, { id : 123, name : 'Cookies', icon : 'fa fa-cookie-bite', quantity : 3, price : 1.99 } ] }, { id : '789012', date : '2022-12-12', details : [ { id : 211, name : 'Chicken', icon : 'fa fa-drumstick-bite', quantity : 2, price : 4.99 }, { id : 212, name : 'Carrots', icon : 'fa fa-carrot', quantity : 1, price : 1.49 }, { id : 213, name : 'Wine', icon : 'fa fa-wine-bottle', quantity : 4, price : 2.99 }, { id : 214, name : 'Cheese', icon : 'fa fa-cheese', quantity : 3, price : 3.49 }, { id : 215, name : 'Bottled Water', icon : 'fa fa-bottle-water', quantity : 5, price : 0.99 } ] } ] }); grid.features.rowExpander.expand(grid.store.first);

Note that if used in a Gantt, the Gantt's fixedRowHeight must be set to false.

This feature is disabled by default

Keyboard shortcuts

This feature has the following default keyboard shortcuts:

Keys Action Action description
Shift+ArrowRight expandByKey Expands the focused row
Shift+ArrowLeft collapseByKey Collapses the focused row
Space toggleExpandByKey Toggles expand/collapse when focused on the expander cell

Please note that Ctrl is the equivalent to Command and Alt is the equivalent to Option for Mac users

For more information on how to customize keyboard shortcuts, please see our guide

Expand on click

Set triggerEvent to a Grid cell event that should trigger row expanding and collapsing.

new Grid({
   features : {
       rowExpander : {
           triggerEvent: 'celldblclick',
           renderer...
       }
   }
});

Expander column position

The expander column can either be inserted before or after the existing Grid columns. If the Grid has multiple regions the column will be added to the first region.

Adjust expander column position to last in a specific Grid region by setting columnPosition to last and configuring the column with a region name.

new Grid({
   features : {
       rowExpander : {
           column: {
               region: 'last'
           },
           columnPosition: 'last',
           renderer...
       }
   }
});

Record update

If the expander content depends on row record data, the expander can be re-rendered on record update by setting refreshOnRecordChange to true.

new Grid({
   features : {
       rowExpander : {
           refreshOnRecordChange: true,
           renderer...
       }
   }
});

Async

When the content of the row expander should be rendered async just see to it that you return a promise.

new Grid({
   features : {
       rowExpander : {
           async renderer({record, region, expanderElement}){
               return fetchFromBackendAndRenderData(record);
           }
       }
   }
});

Multiple regions

When the Grid has more than one region, the renderer function will be called once per region for each expanding row.

new Grid({
   features : {
       rowExpander : {
           renderer({ record, region }) {
               if(region === 'locked') {
                   return createRowExpander(record);
               }

return null; } } } });

If you are using the widget configuration, you can provide a widget configuration object for each region like so:

new Grid({
   features : {
       rowExpander : {
           widget : {
               locked : {
                   type : 'detailGrid',
                   // If your widgets uses different data sources, put the
                   // dataField property in the widget configuration object
                   dataField : 'orderDetails'
               },
               normal : {
                   type : 'summaryGrid',
                   dataField : 'sumDetails'
               }
           }
       }
   }
});

This live demo has a set of buttons in the locked region and a detail grid in the normal region:

const grid = new Grid({ appendTo : targetElement, height : 400, features : { // Enable the feature rowExpander : { widget : { locked : { type : 'panel', title : 'Actions', items : { addSkill : { type : 'button', text : 'Add skill', width : '100%' }, openReport : { type : 'button', text : 'Open time report', width : '100%' }, deactivate : { type : 'button', text : 'Deactivate', width : '100%' } } }, normal : { type : 'grid', dataField : 'skills', autoHeight : true, columns : [ { field : 'id', text : 'No.', type : 'number', width : 80 }, { field : 'skill', text : 'Skill', flex : 1 }, { field : 'level', text : 'Level', type : 'number', width : 100 }, { field : 'verified', text : 'Verified', type : 'check', width : 100 } ] } } } }, subGridConfigs : { locked : { width : 190 }, normal : { flex : 1 } }, columns : [ { field : 'name', text : 'Name', width : 150, region : 'locked' }, { field : 'id', text : 'Employee no.', type : 'number', width : 110, region : 'normal', align : 'center' }, { field : 'city', text : 'City', width : 110, region : 'normal' }, { field : 'age', text : 'Age', type : 'number', width : 90, region : 'normal', align : 'center' }, { field : 'start', text : 'Start', type : 'date', width : 110, region : 'normal' }, { field : 'name', text : 'Email', width : 220, region : 'normal', htmlEncode : false, renderer : ({ value }) => { const email = value.toLowerCase().replaceAll(' ', '.') + '@example.com'; return StringHelper.xss`<i class="fa-envelope"></i><a href="mailto:${email}">${email}</a>`; } }, { field : 'active', text : 'Active', type : 'check', width : 90, region : 'normal' } ], data : DataGenerator.generateData({ count : 10, addSkills : true, rowCallback(row) { row.skills = row.skills.map((skill, index) => ({ id : index + 1, skill, level : Math.round(Math.random() * 2) + 1, verified : Math.random() > 0.5 })); } }) }); // How to programmatically expand a specific row: //grid.features.rowExpander.expand(grid.store.first);

If you want your expanded content to span over all Grid regions, set the spanRegions config to true.

const grid = new Grid({ appendTo : targetElement, height : 400, features : { // Enable the feature rowExpander : { spanRegions : true, widget : { type : 'grid', dataField : 'skills', autoHeight : true, columns : [ { field : 'id', text : 'No.', type : 'number', width : 80 }, { field : 'skill', text : 'Skill', flex : 1 }, { field : 'level', text : 'Level', type : 'number', width : 100 }, { field : 'verified', text : 'Verified', type : 'check', width : 100 } ] } } }, subGridConfigs : { locked : { width : 190 }, normal : { flex : 1 } }, columns : [ { field : 'name', text : 'Name', width : 150, region : 'locked' }, { field : 'id', text : 'Employee no.', type : 'number', width : 110, region : 'normal', align : 'center' }, { field : 'city', text : 'City', width : 110, region : 'normal' }, { field : 'age', text : 'Age', type : 'number', width : 90, region : 'normal', align : 'center' }, { field : 'start', text : 'Start', type : 'date', width : 110, region : 'normal' }, { field : 'name', text : 'Email', width : 220, region : 'normal', htmlEncode : false, renderer : ({ value }) => { const email = value.toLowerCase().replaceAll(' ', '.') + '@example.com'; return StringHelper.xss`<i class="fa-envelope"></i><a href="mailto:${email}">${email}</a>`; } }, { field : 'active', text : 'Active', type : 'check', width : 90, region : 'normal' } ], data : DataGenerator.generateData({ count : 10, addSkills : true, rowCallback(row) { row.skills = row.skills.map((skill, index) => ({ id : index + 1, skill, level : Math.round(Math.random() * 2) + 1, verified : Math.random() > 0.5 })); } }) });

See also

  • Grid - The grid component
No results

Configs

Configs are options you supply in a configuration object when creating an instance of this class
  • When expanding a row and the expanded body element is not completely in view, setting this to true will automatically scroll the expanded row into view.

  • columnPosition : 'first'/'last'first

    Makes the expand/collapse button column appear either as the first column (default or first) or as the last (set to last). Note that the column by default will be added to the first region, if the Grid has multiple regions. Use the column config to change region.

  • Used together with widget to populate the widget's Store from the expanded record's corresponding dataField value, which needs to be an array of objects or a store itself.

  • Use this to disable expand and collapse animations.

  • See Keyboard shortcuts for details

  • Use this for customizing async renderer loading indicator height.

  • Use this for customizing async renderer loading indicator text.

  • If set to true, the RowExpander will, on record update, re-render an expanded row by calling the renderer function or recreate the configured widget.

  • When the Grid has multiple regions, setting this config to true changes how the expanded content is created and rendered. Instead of calling renderer once per region (or one widget per region) it will only create one expanded element which will span the full grid width regardless of Grid regions.

  • Internal listeners, that cannot be removed by the user.

  • The widget which this plugin is to attach to.

    Has a corresponding runtime client property.

  • Set to false to disable localization of this object.

Properties

Properties are getters/setters or publicly accessible variables on this class
  • isEvents : Booleantrue
    READONLY
    static
    ADVANCED
    Events
    Identifies an object as an instance of Events class, or subclass thereof.
  • isLocalizable : Booleantrue
    READONLY
    static
    ADVANCED
    Localizable
    Identifies an object as an instance of Localizable class, or subclass thereof.
  • isRowExpander : Booleantrue
    READONLY
    static
    ADVANCED
    Identifies an object as an instance of RowExpander class, or subclass thereof.
  • properties : Object
    internal
    static
    InstancePlugin

    A class property getter for the default values of internal properties for this class.

  • emptyArray : Array
    internal
    READONLY
    InstancePlugin

    An empty array that can be used as a default value.

  • emptyObject : Object
    internal
    READONLY
    InstancePlugin

    An empty object that can be used as a default value.

  • isInstancePlugin : Booleantrue
    READONLY
    ADVANCED
    InstancePlugin
    Identifies an object as an instance of InstancePlugin class, or subclass thereof.
  • isRowExpander : Booleantrue
    READONLY
    ADVANCED
    Identifies an object as an instance of RowExpander class, or subclass thereof.
  • config : Object
    READONLY
    ADVANCED
    InstancePlugin

    Returns a copy of the full configuration which was used to configure this object.

  • This property is set to true before the constructor returns.

  • isDestroying : Boolean
    READONLY
    ADVANCED
    InstancePlugin

    This property is set to true on entry to the destroy method. It remains on the objects after returning from destroy(). If isDestroyed is true, this property will also be true, so there is no need to test for both (for example, comp.isDestroying || comp.isDestroyed).

  • client : Widget
    READONLY
    ADVANCED
    InstancePlugin

    The Widget which was passed into the constructor, which is the Widget we are providing extra services for.

    Has a corresponding client config.

  • Get the global LocaleHelper

  • Get the global LocaleManager

Functions

Functions are methods available for calling on the class
  • onClassMixedIn( )
    internal
    static
    InstancePlugin

    This optional class method is called when a class is mixed in using the mixin() method.

  • initClass( )
    static
    ADVANCED
    InstancePlugin

    Registers this class type with its Factory

  • collapseByKey( )
    private

    Collapses the focused row on Shift+ArrowLeft.

  • expandByKey( )
    private

    Expands the focused row on Shift+ArrowRight.

  • toggleExpandByKey( )
    private

    Toggles expand/collapse when focused on the expander column cell. Only activates when the focus is on the expander column to avoid conflicts with other Space handlers.

  • Internal function used to hook destroy() calls when using thisObj

  • Internal function used restore hooked destroy() calls when using thisObj

  • doDestroy( )
    internal
    Events

    Auto detaches listeners registered from start, if set as detachable

  • once( )
    private
    Events

    Internal function used to run a callback function after an event is triggered

  • Removes all listeners registered to this object by the application.

  • This will merge a feature's (subclass of InstancePlugin) keyMap with it's client's keyMap.

  • beforeRenderRow( )
    private

    Hooks on before row render to render or remove row expander content depending on record state.

  • internalRender( )
    private

    Re-renders the grid from the topmost record of those saved in bufferedRenderer

  • scrollRowIntoView( )
    private

    Scrolls expanded row into view. This function is called after rowManager has finished rendering.

  • waitForTransition( )
    private

    Waits for height transition on the provided rows element. Then calls provided function.

Events

Events are triggered for certain actions in this class and can be listened for to react to those actions in your code

Event handlers

Event handlers are callbacks called as a result of certain actions in this class

CSS variables

CSS variables that can be set to adjust appearance
Name Description
--b-row-expander-background Row expander body's background color
--b-row-expander-border-bottom-color Row expander body's border bottom color
--b-row-expander-border-bottom-width Row expander body's border bottom width
--b-row-expander-color Row expander body's color
--b-row-expander-font-weight Row expander body's font weight
--b-row-expander-padding Row expander body's padding
id: rowExpander

Source path

Grid/feature/RowExpander.js

Contents