A lightweight, optionally (client or server-side) pagable table presenting a grid-like view on a set of records, supporting: single/multi selection; (conditionalizable) field / record-level actions; per-record cell coloring; rendering of field values as icons / (field-level action) icon buttons; sorting by one or more columns; drag-reordering / resizing of columns; optional record filter toolbar; optional injection of record grouping annotations (for sorted columns); optional generation of record-set-level summary row(s) (when in unpaged / internally paged modes).

Flex Table is a light-weight "pure UUX" component that (a) isn't based on any underlying google @material component, and (b) doesn't introduce any new 3rd party library dependencies.

Read on to learn more about individual features below, or skip ahead to the Live Demo

Flex Table supports both single and multiple record selection modes, with or without a dedicated column of record selector inputs (i.e. radio buttons in single selection mode, or checkboxes - including a [de]select all checkbox (unless disabled via the hideSelectAllRecordsCheckbox attribute/property) - in multi-selection mode).

This desired mode may be specified via the optional the recordSelectionMode property. This takes values of "single", "multi" or "none" (defaulting to: "single").

Where desired, the display of a dedicated "record selector" column (as described above) may be enabled (subject to record selection having been enabled as outlined below) by specifying true as the value for property: showRecordSelectorColumn.

Prerequisites for enabling record selection (and for FlexTable to accept any attempt display the record selector column) are as follows:
  • the table's data-record objects should include a (common) field/property giving the primary key value (i.e. a unique identifier) for each data record

  • the name of that field/property must be specified as the value of (optional) tableSpec property: primaryKeyFieldName (without this, FlexTable will reject any attempt to set recordSelectionMode to "single" or "multi", automatically reverting the value for this property to "none")

  • values defined for the nominated field/property should be of a consistent type (the supported possibilities being either string or number) across all data-record objects

  • where the primary values are of type: number (vs. string), this must be explicitly indicated by setting (optional) tableSpec property: primaryKeyValueType (which takes values of: "string" or "number" defaulting to: "string") to "number"

Note that any data records that either (a) fail to define any value for the field/property nominated by tableSpec.primaryKeyFieldName, or (b) define a value of a type other that that implied by the effective value of tableSpec.primaryKeyValueType will be treated as unselectable by FlexTable.

Client applications wishing to query or set the selection state may do so via top-level property: pipeSeparatedSelectedRecordKeysList. Valid values for this property comprise a pipe-separated string of primary-key values. Note that, on update the supplied property value is (a) automatically purged of any duplicate / definitely invalid primary key values, and - where necessary - b) reordered such that the remaining keys appear in ascending lexical or numeric order (depending on whether the effective primary key type is "string" or "number", respectively).

Client applications wishing to conditionalize user selectability / deselectability of individual records may do so by specifying a value for the (optional, function-typed) tableSpec property: toggleRecordSelectionStatePredicate. Where defined, this is expected to be reference to function of the form: (dataRecord: object, isDataRecordCurrentlySelected: boolean): boolean. Where supplied, FlexTable will invoke this each time the user attempts to select or deselect a record, supplying as arguments (a) the relevant tableSpec.dataRecords[] element and (b) a boolean indication of whether that record is currently selected (true if so, or false otherwise). If the invocation returns false, Flex Table essentially ignores the user action (so leaving the selection state of that record and the record-set of which it is part unchanged). Typically in this scenario, the client-defined function would issue some form of notification to the user explaining why the selection state of that record cannot be toggled. This mechanism can be used for a variety of purposes, e.g. on a multi-selectable table, to implement the idea of a "base record selection" - i.e. a set of "pre-selected" records (specified via Flex Table's top level pipeSeparatedSelectedRecordKeysList property) to which the user may add further records, but from which the user is prevented (by a suitably defined toggleRecordSelectionStatePredicate function) from removing any "pre-selected" record.

(Note that, for simplicity, in the case where toggleRecordSelectionStatePredicate is specified, recordSelectionMode is set to "multi" and showRecordSelectorColumn is set true, Flex Table automatically hides its "select all" checkbox - i.e. exactly as if its top-level hideSelectAllRecordsCheckbox property were set true. This is done in order to sidestep complications around the appearance / semantics / behaviours of that control that would otherwise necessarily arise were that control to be shown in this situation).

Client applications wishing to be notified of (user-initiated vs. programmatic) changes to the record selection state (e.g. for the purpose of enabling/disabling buttons / other controls external to FlexTable) may register listener(s) for "recordSelectionChanged" events. For further information on the contents of the "detail" objects associated with these (custom) events, please refer to the description for this event type in the Implementation section of the Code tab above.

Record level actions may be presented inline (as icons or links in a dedicated column), as options accessed via per record menu buttons (in that same dedicated column), or any combination of the above. By specifying renderFieldValueAsAction: true in the relevant column spec, it is also possible to have field values rendered as clickable "field-level action" links.

Icons (chosen/coloured based on the values of one or more record fields) may be rendered to data cells by specifying a suitable function as the value of a column spec's (optional) dataCellIconSpecGeneratorproperty.

For an example of this, please see the With "charisma" values rendered as icons example in the Live Demo.

Where such column specs are augmented to additionaly include renderFieldValueAsAction: true, clickable "field-level action" icon buttons (vs. non-clickable icons) will be rendered.

For an example of this, please see the With "charisma" values rendered as (field-level-action) icon buttons example in the Live Demo.

(NB: Code for the genCharismaIconSpec() function used to support these examples is provided in the "Code" tab above).

Record fields containing HTML content (most likely generated by preprocessing of the record data supplied to FlexTable as tableSpec.dataRecords[]) may be rendered as such by specifying true as the value of the (optional) renderFieldValueAsHtml property in relevant tableSpec.columnSpecs[] elements.

If any such fields could potentially include content (e.g. <br> or <p> tags) that could result in multi-line HTML content, then you'll likely also want to specify the (implicitly boolean true) topAlignBodyRows attribute on your <uwc-flex-table> tag (or equivalently, property topAlignBodyRows to true on your FlexTable element) so that the contents of all of your body cells content are top (vs. middle) aligned with respect to their containing <tr> elements.

Unless you've disabled filtering of such columns (by specifying true as the value for the optional filterable property) in the relevant tableSpec.columnSpecs[] elements), you'll likely also want to specify the name of a record field containing a plaintext equivalent of the HTML content as the value of optional column spec property: filterFieldName so as to prevent tags in the values of the field identified by fieldName from being matched by global filter searches.

For an example demonstrating all of the above, please see the With "Actor/Voice" values rendered as HTML example in the Live Demo.

FlexTable offers the option of making arbitrary text/html content (e.g. supplementary record data and/or controls) available to the user via auto-generated, record-level expand / collapse actions. On invocation, these trigger the dynamic injection/removal of an additional table row (immediately below that on which the action was invoked) within which the (perhaps freshly [re]generated) expandable content is contained inside single full-width table cell (i.e. a table cell spanning all visible columns).

The source for such content can be either (a) a nominated record field (e.g. one created/prepopulated by the client application as part of pre-processing raw record data retrieved from an API call), or (b) a (possibly asynchronous) function that takes a data record object as its single argument and returns a string, null, or undefined (or a Promise of any of those, as would be the case if the function is asynchronous).

[Aside: If you're considering input/control elements in your expandable content HTML(s), the recommended approach re attaching "data-record aware" event handlers to these is covered here: here)]

Configuration is via optional tableSpec property: expandableRecordContent property.

This accepts values of the following types:

  • the string name of the record field (i.e. the name of a string-valued property present within some / all tableSpec.dataRecords[] objects) providing the "expandable content" (if any) for that record

  • a function of the form: [async] (dataRecord): string | null | undefined | Promise<string | null | undefined>

    (for FlexTable to call whenever it needs to [re]obtain the latest expandable content for a data record).
  • an ExpandableRecordContentConfig - i.e. a plain javascript object defining some subset of the following properties described below.

    With the single exception of the expandableContentSource property, all of these properties are optional (with defaults that should work just fine for simpler usage scenarios). You may therefore prefer to skip the remainder of this (lengthy) section on initial reading and instead: (a) try out the default behaviour for either/both of the two simpler alternatives outlined immediately above then (b) return here should you find the need to fine-tune for your particlar usage scenario.

    • [mandatory] expandableContentSource: the source from which FlexTable is to obtain the expandable content for a record (i.e. the string name of a record field, or a function - see above).

      Note that this is the sole mandatory property - the remainder are all optional and (where applicable) defaulted
    • [optional] actionTitleExpand defines the title (default: "Show details") for the auto-generated "expand" action.

      (This is used as the action icon's tooltip if the effective value of expandCollapseActionPresentationStyleis "icon", or as the action menu item's label if the effective value of that property is "menu")
    • [optional] actionTitleCollapse defines the title (defaults to: "Hide details") for the auto-generated "collapse" action

      (This is used as the action icon's tooltip if the effective value of expandCollapseActionPresentationStyleis "icon", or as the action menu item's label if the effective value of that property is "menu")
    • [optional] expandCollapseActionPresentationStyle: specifies the presentation style for the (auto-generated) expand/collapse actions on each record, with valid values as follows (defaults value: "icon"):

      • "icon": the action applicable to a given record will be represented as an icon ("keyboard_arrow_down" for "expand", "keyboard_arrow_up" for "collapse") with positioning (relative to the "Actions" column items) being as per the effective value for the optional expandCollapseActionPosition property (see below)

      • "menu": the action applicable to the record will be offered as an additional item in the menu activated by the "Actions" column's menu button, with positioning (relative to other action menu items) being as per the effective value of the optional expandCollapseActionPosition property (see below)

    • [optional] expandCollapseActionPosition: specifies the position for the (auto-generated) expand/collapse actions, with valid values as follows (defaults value: "end"):

      • "start":

        • where the effective value for optional property: expandCollapseActionPresentationStyle is "icon" this positions the action icon as the first "Actions" cell item

        • where the effective value for optional property: expandCollapseActionPresentationStyle is "menu" this positions the action menu item as the first item within the menu opened by the "Actions" cell's menu button.

      • "end":

        • where the effective value for optional property: expandCollapseActionPresentationStyle is "icon" this positions the action icon as the final "Actions" cell item

        • where the effective value for optional property: expandCollapseActionPresentationStyle is "menu" this positions the action menu item as the final item within the menu opened by the "Actions" cell's menu button.
    • [optional] whereExpandableContentFromFieldBlank: relevant only if expandableContentSource is a (string) data-record field name (rather than a function), this property defines the policy to be applied regarding data records where the nominated property on the relevant data-record is undefined / blank / defines a non-string value of a type that either doesn't provide a toString() method or returns something other than a non-blank string from that method.

      Valid values are:

      • "disableExpander": disables the "expand" action for the relevant data-record row

      • "showNoContentAvailableMessageOnExpand": leaves the "expand" action for the relevant data-record row enabled, shows the message defined by the effective value of noContentAvailableMessage (see below) if / when the user activates the "expand" action for that record

    • [optional] noContentAvailableMessage: defines the message (default: "Sorry, no details to show") shown in place of expandable content in either of the following cases:

      • expandableContentSource is a (string) data-record field name (rather than a function), the effective value of whereExpandableContentFromFieldBlank (see above) is "showNoContentAvailableMessageOnExpand" and the nominated property on the relevant data-record is undefined / blank / defines a non-string value of a type that either doesn't provide a toString() method or returns something other than a non-blank string from that method

      • expandableContentSource is a function and invocation of that function either fails or returns undefined / blank / a non-string value that either doesn't provide a `toString() method or returns something other than a non-blank string from that method.

    • [optional] recordExpansionMode: valid values for this property (default value:"multi") are:

      • "single": signifies that showing the expandable content for a given record should automatically revert the expansion state for any other record to "collapsed" (regardless of whether it happens to be on the same page or a different one)

      • "multi": signifies that the expandable content for a given record should not impact the expansion state of any other record (so allowing multiple records to be in "expanded" state, both within and across table pages)

    • [optional] allowTableWidthGrowthOnExpand: this optional, boolean-valued property (default value: false) controls whether the act of showing the expandable contact for a record is allowed to provoke a growth in the width of the table (where such width is available).

      Set to true if you'd like your FlexTable to allow that.

    • [optional] plainHtmlTextVariantOption: configures various aspects of the default font for plain html text and elements (e.g. <button>, <select>, ...) only - doesn't affect web components (e.g. <uwc-button>, <uwc-select>, ...)

      Valid values are as follows (the default value being: "useBodyCellTextVariant"):

      • "none": meaning "don't do anything in this regard"

      • "useBodyCellTextVariant": mirrors whatever text styling is currently applied to the table's body cells (as determined by the effective value of via FlexTable's top-level `bodyCellTextVariant property)

      • any of the specific variants specifiable for FlexTable's top-level bodyCellTextVariant property (i.e. "body", "x-small", "small", "large", "h1", "h2", "h3", "h4", "h5", or "h6").

    • [optional] expandableContentHorizontalAlignment: controls the horizontal alignment of the (externally provided) expandable content within its full-width table cell.

      Valid options (default value: "start" are: "start", "center", "end".

    • [optional] maxExpandedContentHeightBeforeVertScrollbarShown: defines a maximum height limit for expanded record content (beyond which vertical scrollbars will automatically appear at the end of scrollable content rows, thus limiting the height consumed by those rows within the table as a whole).

      Where specified, the value can be any of the following:

      • a positive number-typed value (this will be assumed to denote a height in pixels)
      • a string representation of the above (with identical semantics)
      • a string denoting a positive, non %-typed CSS dimension value
      • "unset" (signifying that no height restriction should be applied)

      Defaults to: "unset"

    • [optional] cacheLifetimeMillisForFunctionSourcedExpandableContent: specifies the maximum cachable lifetime (if any) of expandable record content html strings (the effective lifetime of an additional cache entry extending into the future by whatever interval is specified from the moment of creation).

      Where specified, values for this property should ideally be of number type (although FlexTable will also accept / understand string representations of the same).

      Semantics associated with the value specified are as follows:

      • 0 (or indeed any non-positive value - e.g. -42, -Infinity) is interpeted to signify that no caching is to occur.
      • any positive finite value is intepreted to mean that caching is to be enabled, with individual cache entries being considered valid for that many milliseconds into the future from the moment they are created.

      • Infinity is interpreted to mean that caching is to be enabled with individual cache entries having "infinitely long" validity lifetimes (so remain valid indefinitely)

    • [optional] expandableContentCacheDebug: useful for understanding what's going on if you've enabled expandable content caching by specifying a positive value for cacheLifetimeMillisForFunctionSourcedExpandableContent (see above), this optional boolean-valued property operates entirely independently of FlexTable's top-level (and top-secret !) _debug property.

      Set this true to enable console debug logging for FlexTables "expandable content cache".

      Defaults to: false

    • [optional] autoCollapseExpandedContentRowsOnInternalPaginationAction: this boolean-valued property specifies whether or not expanded record content rows are to be automatically collapsed each time the user performs any action via the table's record pagination toolbar for an internally paginated table (pagedRecordsSource: "internal").

      Defaults to: false

If you want to include controls (buttons, input elements, etc - or UUX web component equivalents) in your expandable record content HTML strings, then you may well be wondering:

  • How do I go about attaching event handlers to those controls ?
  • How can those event handlers know / discover the associated data-record object ?

The recommended approach is as follows:

  • Ensure that the event-handler functions you'll be referencing within the expandable content HTMLs for a particular FlexTable instance are defined such that they are reachable globally when that particular FlexTable instance is shown

    You could do this in a variety of ways, e.g:

    • by defining them within you're application's top-level page (or within a js library included by that page) either:

      • as top-level functions
      • as function-valued fields within a containing object (so that object effectively operates as a "namespace" for that collection of functions)
    • alternatively, if the FlexTable in question is rendered as a sub-component of a containing application-specific web component, you might choose to have that containing component "publish" relevant event functions to the window object directly (e.g. window && (window["handleXYZSelectChanged] = (e: Event) => { ... }))

  • Regarding the problem of getting hold of the associated data-record object within your event handler functions:

    • FlexTable provides a public instance method: findDataRecordForExpandedRecordContentEvent(evt: Event): object | undefined to help with this.

      (Providing the supplied Event is one fired by an element in the expanded content for a data-record, this is guaranteed return that data-record object)

    • Given that this is an instance method, clearly your event handler implementations will require access to the relevant FlexTable instance in order to take advantage of this.

      One way to do that would be to implement a (querySelector()-based) "finder" function (for use within your event handler implementations) and to "publish" that using either of the approaches outlined above for making the event handlers globally available.

  • Within your expandable content HTMLs, control elements can be associated with the relevant event handlers in the "old school" way - e.g: <select onChange="handleXYZSelectChanged(event)"> (or if you've decided to put your event-handlers in globally-available name-spacing/scoping object: abcExpandableContentEventHandlers, then: <select onChange="abcExpandableContentEventHandlers.handleXYZSelectChanged(event)">)

Similarly, text/background colors (based on the values of one or more record fields) may be applied to non icon/icon-button data cells by supplying a function (taking a data record object as its single argument, and returning either an object of the form: { textColorOrCSSVarname?: string, cellBackgroundColorOrCSSVarname?: string } or null / undefined) as the value of the (optional, function-valued) textCellColorSpecGenerator property on the relevant tableSpec.columnSpecs[] object(s).

For an example of this, please see the With color highlighting of First / Last Name values for main / important characters example in the Live Demo below. (NB: you can find the code for the genTextCellColorSpecBasedOnCrucialityOfCharacterToSeries() function used to support this example in the "Code" tab above).

In addition to the record-specific data cell colouring facility covered in the preceding section, it is also possible to have FlexTable apply different (record specific) "default" background and/or foreground colour(s) to ALL cells (including those for both (a) the record selector column [where shown], and (b) the record-level "Actions" column [where shown]) within individual record rows based on record content.

This is achieved by supplying a suitable function as the value of the (optional, function-valued) tableSpecproperty: recordRowColorGeneratorFunction. Where defined, this is expected to be a function that (a) takes a data record as its single argument, and (b) returns either (i) an object of the form: { defaultRowForegroundColorOrCSSVarname?: string, defaultRowBackgroundColorOrCSSVarname?: string } or (ii) null / undefined.

For an example of this, please see the With color highlighting of record rows for main / important characters example in the Live Demo below. (NB: you can find the code for the genDefaultRowBgColorBasedOnCrucialityOfCharacterToSeries() function used to support this example in the "Code" tab above).

Note that, when used in conjunction with the record-specific data cell-level colouring feature (see preceding section), any cell-level background and/or foreground colouring (as implied by returns from the textCellColorSpecGenerator and/or dataCellIconSpecGenerator functions on relevant tableSpec.columnSpecs[] elements) is given precedence over any row-level defaults implied by the value returned by the tableSpec.recordRowColorGeneratorFunction function for the same data record.

FlexTable supports the definition of (record-specific, optionally translatable) tooltips on the data cells within relevant tableSpec.columnSpecs[]-defined columns.

The key to achieving this is the (optional) dataValueTooltipTemplate property on each tableSpec.columnSpecs[] element.

This property accepts values of any of the following types:

  • a string defining a (non translatable) template from which to generate the tooltip content for each data cell in the column defined by the containing column spec.

  • a Translatable: a plain javascript object of the form: { lookupKey: string, defaultValue?: string } providing FlexTable with the means to try find the appropriate language-specific variant of the tooltip template string (via its translationProvider)

  • a DataValueTooltipSpec: a plain javascript object of the form described below, in which:

    • contentTemplate is either a string or Translatable (as described above)
    • cssStyleProperties provides the means to override CSS properties: max-width and/or white-space on the <uwc-tooltip> elements generated for this column.
{
contentTemplate?: MaybeTranslatable,
cssStylePropValues?: {
maxWidth?: string
whiteSpace?: string
}
}

Regardless of how the effective tooltip template string for a given column is defined / made available (see above), FlexTable will process any "field value reference" tokens it may contain, replacing these with the appropriate values.

The BNF syntax for these tokens is as follows (skip ahead to the examples below if you prefer)

fieldValueReference ::= "@{" + fieldName + [ ":" + qualifier ] + "}"
fieldName ::= (* any tableSpec.columnSpecs[n].fieldName - expected to match regular expresssion: /^[a-z][a-z0-9_-]*$/i *)
qualifier ::= renderFieldValueAsHTMLQualifier | translationLookupKeyPrefixQualfier
renderFieldValueAsHTMLQualifier ::= "html"
translationLookupKeyPrefixQualfier ::= "translationLookupKeyPrefix=" + translationLookupKeyPrefix
translationLookupKeyPrefix ::= (* the prefix to be applied to the value of the record field identified by fieldName in order to derive a translation lookup key *)



The token: @{firstName} refers to the value of the firstName field within the relevant data record.

In the event that such a value happens to contain HTML markup, then that markup will be rendered verbatim (vs. as HTML) in the tooltip content for the corresponding data cell.


The token: @{firstName:html} refers to the firstName field within the relevant data record, but also includes the optional "html" modifier.

This tells FlexTable that any HTML markup contained within the value of that field for a given data record should be rendered as HTML (vs. verbatim, as would be the cases without the "html" modifier) in the tooltip content for the corresponding data cell.


The token: @{seriesType:translationLookupKeyPrefix="table.dataValueTooltips.seriesType."} tells FlexTable that, rather than using the value of the seriesType field for a given data record directly, it should instead:

  • build a translation lookup key by concatenating the field value onto the specified prefix
  • attempt resolve that lookup key to the appropriate translation (via its translationProvider)
  • use the resulting translation (falling back to the raw field value if no suitable translation was found) in the tooltip content for the corresponding data cell.

Records may be interactively sorted by clicking the "sort direction indicator" area of the relevant column header cell.

  • Each such click advances (or if the shift key is held, retreats) the sort state for that column by one step in the cycle, which defaults to: unsorted => [ascending, descending] but is overridable to: [unsorted, ascending, descending] by setting property: includeUnsortedInColumnSortStateCycle to true.

  • By default, updating the sort-order for a column resets the sort-state of all other columns to "unsorted".

    If the Ctrl key is held at the time of the click (and providing multiColumnSortDisabled has not been set true), however, the sort-states of other columns are left undisturbed, and the (updated) sort-order for the newly-clicked column is either (a) updated within the overall sort order (if it was already sorted), or (b) appended to the overall sort-order (as the "least significant" part) otherwise.

  • If/when the overall sort order involves more than one column, the "sort direction indicator" areas of each of the sorted columns include a badge indicating the order in which those column-sorts were applied (and so their precedence).

    Clicking one of these badges (or equivalently pressing either Backspace or Delete while the "sort direction indicator" has keyboard focus) causes the sort-state for that column and any other columns subsequently added to the overall sort to be reset to unsorted.

  • All data columns are considered sortable by default, but this can overridden on a per-column basis as required by specifying sortable: false in relevant tableSpec.columnSpecs[] instance(s).

  • Where required, an initial sort order may be specified via the sortSpec property. For further details, please refer to the documentation for that property in the Implementation section of the Code tab above.

Logically, a "base sort order" is a set of one or more sort keys (record field names) that, from the the user's point-of-view, are effectively "locked into" the sortSpec object that specifies / reflects the effective (possibly multi-field) sort order for the table's data records.

Functionally, the effect of a "base sort order" being in force is that, in terms of sorting, the user is limited to actions that:

  • add/remove a non-base-sort-field + sort-direction pair to/from the effective sort order
  • toggle the sort direction between ascending / descending (for base sort fields that also correspond to displayable [tableSpec.columnSpecs[] defined] columns for which sorting has not been disabled)

In summary, the net effects of the above are (a) that the data records are always sorted by at least the set of base sort fields, and hence (b) the user is limited to (at most) sorting within the base sort order implied by the current sort-directions for those base sort fields.

An example of a case in which this might be useful is where you're using the record grouping annotation rows feature and want to ensure that the table is always primarily sorted by the key that makes those grouping annotations relevant (e.g. because the annotation text(s) for those contain information supplementary to that provided via the table's data columns, or because you want to ensure that the ability expand/collapse those record groups [via their annotation rows] is always available).

The mechanism for communicating that a sort field is a base sort field, is simply to include the suffix: ! on the property name for that field within in the plain javascript object specified as the value for the top-level sortSpec property (further details on this property can be found in the Implementation section of the Code tab above).

Columns may also be interactively reordered (by dragging their column headers) and resized (by dragging the relevant column divider). These features are enabled by default, but can if necessary be (independently) disabled by specifying true for the component's columnDragDisabled and/or columnResizeDisabled properties as appropriate.

Record-grouping annotation rows are additional, non data record-related table body rows that may be generated/injected for the purpose of providing supplementary information about (and optionally, the ability to expand/collapse) logical groupings that occur when the data records are sorted in particular ways.

Flex Table supports two alternative approaches for achieving this: function-based and declarative:


In this approach, the client defines a function that Flex Table can call as part of its rendering process if the table is in a sorted state in order to discover the texts and insert positions for any grouping annotation rows that may need to be inserted for the current sort-state.

The function should be one that (a) to accepts as arguments (i) an array containing the renderable data objects, and (ii) the current value of the component's sortSpec property, and (b) returns either (i) a Map of annotation row text strings keyed by row insertion index (where these are relative to elements of the supplied array of renderable data records objects) or (ii) undefined or null if there are no grouping annotations for the supplied sortSpec.

It is made available to Flex Table as the value of the (optional, function-valued) tableSpec.recordGroupAnnotationFunctionproperty.

For an example of this approach in action, please see the Pre-sorted by "Updated" (asc) with record grouping annotations (using recordGroupAnnotationFunction) example in the Live Demo.

Presentation aspects (such whether the grouping rows are to be made expandable/collapsible) may be specified via the (optional, object-valued) tableSpec.recordGroupAnnotationRowOptions property (see below). For an example useage of this, see the Pre-sorted by "Updated" (asc) with collapsible record grouping annotations (using recordGroupAnnotationFunction + recordGroupAnnotationRowOptions) in the Live Demo.

The code for the tableSpec.recordGroupAnnotationFunction implementation used to support the above Live Demo examples is available in the "Code" tab above.


In this approach (a) the set of grouping texts for each type of grouping row is made available via a dedicated data record field (which may, if necessary, be added as part of a client-implemented data records pre-processing step), and (b) a plain javascript object providing a mapping of primary-sort-field-name to applicable-annotation-field-name is provided as the value for the (optional) annoFieldNameByPrimarySortFieldName property of tableSpec.recordGroupAnnotationRowOptions (see below).

Note that where such a mapping is available for the active primary-sort-field-name, this takes precedence over (i.e. is used instead of) tableSpec.recordGroupAnnotationFunction where that is also defined.

For an example useage of this approach, see the Pre-sorted by "TV Series" (asc) with collapsible record grouping annotations (using recordGroupAnnotationRowOptions.annoFieldNameByPrimarySortFieldName) example in the Live Demo.


The value of this optional property is a plain javascript object of the following form:

{
expandableCollapsible?: boolean,
includeRecordCountsIfUnpaginated?: boolean,
annoFieldNameByPrimarySortFieldName?: AnnoFieldNamesPrimarySortFieldName
}

...and the semantics of those properties (all of which are optional) are follows:

  • expandableCollapsible: specify true if the injected grouping rows are to be made expandable/collapsible (defaults to false)

  • includeRecordCountsIfUnpaginated: specify true if (subject to the table being unpaginated)  the record count for each record grouping is to be calculated and included as a suffix (of the form " (n)") on the text content for each record grouping annotation row (defaults to false)

  • annoFieldNameByPrimarySortFieldName: where defined, this should be a plain javascript object functioning as mapping of primary-sort-field-name to applicable-annotation-field-name. When rendering sorted records, Flex Table initially checks here to see if there's an applicable-annotation-field-name for the active primary-sort-field-name. If so, then it uses that to figure out what annotation rows need to be injected (and where) for set the data record rows it is rendering itself (ignoring anytableSpec.recordGroupAnnotationFunction that may also defined). Otherwise, it will get that information by invoking the function specified by tableSpec.recordGroupAnnotationFunction (where defined).

It is possible to have Flex Table generate one or more "summary rows" to be displayed beneath the table's data rows each time the table is rendered, each "summary cell" containing a (potentially translatable) label and/or a aggregate/summary value. The applicable record-set for summary-row generation purposes is that subset of the subset of data records matching any global filter that may be in effect. Accordingly, Flex Table will not generate any summary rows when "external" paging mode is in effect (on the grounds that, in this case, the only records Flex Table has the details for are the ones on the currently-displayed table page).

The meta-information to drive the auto-generation of summary rows is provided via the optional, per-columnSpec property: columnValueSummarization.

This accepts values of any of the following types:

  • the string name of a supported aggregation/summary cell type

    NB: For such definitions, FlexTable will assume (a) that the values to be aggregated are those for the record field identified by the parent columnSpec's fieldName property, and (b) that the resulting aggregate value does not need to be formatted in any way.

    The supported aggregation/summary types, and the (potentially translatable) labels / values of the summary cells they produce are as follows:

    • "sum": produces label: Total and a value that is either (a) the sum of values of the relevant field or (b) NaN if one or more of those values cannot be cooerced to Number
    • "mean": produces label: Mean and a value that is either (a) the mean (average) of the values for the relevant field, or (b) or (b) NaN if one or more of those values cannot be cooerced to Number
    • "median": produces label: Median and a value that is either: (a) the value that occurs as the middle value when the set of applicable values are sorted into ascending order (if there are an odd number of values), else (b) either: (i) the average of the 2 "middle" values (providing both are coercible to Number), else (ii) the lower/earlier of the 2 "middle" values.
    • "min": produces label: Min and the value that occurs first when the set of values for the relevant field are sorted into ascending order.
    • "max": produces label: Max and the value that occurs last when the set of values for the relevant field are sorted into ascending order.
    • "unique-count": produces label: Unique and a value that is a count of the number of distinct/unique values for the relevant field.
    • "blank": produces a blank ("filler") summary cell (potentially useful when generating multiple summary rows - see below)
  • a function that takes as its single argument an array containing the subset of applicable tableSpec.dataRecords, and returns the an object defining values for some/all of the following:

    • label: MaybeTranslatable: the (possibly translatable) label (if any) for this custom summary cell
    • value: string: the (possibly calculated) value (if any) for this custom summary cell
    • inlineValueStyle: string: the inline style (if any) to be applied to the value (if any) for this custom summary cell
    • summaryValueActionId: string: as described for the same-named property for the "summarization spec" object immediately below
  • a "summarization spec" object defining the following properties:

    • [mandatory] summaryType: one of the aggregation/summary cell type(s) listed above

    • [optional] summaryValueLabel: MaybeTranslatable: an alternative label for the summary cell. Specify empty string ("") to remove label

    • [optional] summarizableValueFieldName: string: the name of the record field from which the summarizable values are to be sourced (if different to the parent column-spec's fieldName)

    • [optional] summaryValueFormatter: a function that takes an aggregate value and returns the string to be displayed as the value part of the corresponding summary cell (generally required if summarizableValueFieldName - see above - is specified).

    • [optional] summaryValueActionId?: string | number: Specifying a non-blank string / or a number value for this property tells FlexTable, that where present (subject to either (a) top-level property: suppressSummaryValueActionsWhenGlobalFilterActive being false or (b) no global filter currently being in effect), the "value" part of the summary cell should be rendered as a clickable link.

      On click of such a link, FlexTable will fire a custom summaryCellValueActionInvoked event containing a detail object with properties as follows:

      • summaryValueActionId: the value defined by this (identically named) "summarization spec" property
      • summaryValue: the text content shown as the "value" part of the summary cell
  • an array in which individual elements may be of any of the above 3 types (so allowing generation of an arbitrary number of summary rows)

If a title is required for the set of summary rows as a whole, then this may be specified FlexTable's summaryRowTitle property. Where specified, FlexTable will try to place this in the first summary row (only), in the data column immediately preceding the first for which there is an aggregate/summary cell. If no such cell exists because (perhaps as a result of the user's column reordering) the first data column is currently one with an aggregate/summary value, then the title is simply omitted.

Finally, in the event that none of the above quite meets your particular needs, tableSpec provides an optional function-valued property: summaryRowsGeneratorFunction, allowing you to assume (full) responsibility for generating the label-value pairs for your summary rows. If a function is supplied as the value of this property, Flex Table will:

  • ignore any columnValueSummarization values that may (also) have been specified in some/all oftableSpec.columnSpecs
  • invoke that function, supplying the following 2 arguments:

    • displayOrderedColumnSpecs[]: an array containing the column-specs defined by table.columnSpecs[], but ordered to reflect any column-reordering that may have been performed by the user.
    • dataRecords: an array containing that subset of tableSpec.dataRecords[] that match the filter (if one is currently applied), ordered as per any sort-order that may currently be in effect

Providing the result looks like a 2-dimensional array of { label?: MaybeTranslatable, value?: string | number, inlineValueStyle?: string } | null, Flex Table will then use this to generate the content for the summary row(s) (implicitly assuming that the elements of the inner (per row) arrays are in in positional correspondence with the current column order as defined by displayOrderedColumnSpecs[]).

A record filter toolbar may optionally be enabled via boolean-valued attribute/property: showGlobalRecordFilterToolbar.

  • By default, this behaves as a case-insensitive global filter (operating across all columns whose columnSpecs that don't explicitly specify filterable: false).

  • However, the following optional features are also supported:

    • a checkbox allowing case-sensitive filtering behaviour to be enabled (where required, this may be enabled via the boolean-valued showGlobalFilterCaseSensitivityControl attribute/property)

    • a "target field/column" flagset providing control over the subset of potentially filterable fields/columns that are to considered for filtering purposes (where required, this may be enabled via the boolean-valued showGlobalFilterColumnSelector attribute/property)

    • a column visibility flagset providing control over the visibility of tableSpec.columnSpecs[]-defined columns (where required, this may be enabled via the boolean-valued showGlobalFilterColumnVisiblitySelector attribute/property). Note that, by default, FlexTable assumes all data columns to be initially visible, but if necessary, this may be overridden on a per-data-column basis by specifying false as the value for (optional, boolean-valued) property: visibleByDefault on tableSpec.columnSpecs[] elements for columns that are to be initially hidden.

  • Rather than occuring as the user types into the filter value text-field, by default, filtering is performed when the user (a) explicitly submits a filter string by pressing the Enter key, (b) presses Ctrl+Backspace (which clears the field), or (c) leaves the filter value text-field (e.g. by pressing Tab).

    Providing external paging mode is not active, however, incremental "as-you-type" filtering is also supported via the boolean-valued globalRecordFilterLiveUpdate property.

  • All data columns are considered filterable by default, but this can overridden on a per-column basis as required by specifying filterable: false in relevant tableSpec.columnSpecs[] instance(s).

IMPORTANT:
  • FlexTable will reject/override any attempt to set showGlobalRecordFilterToolbar true made while showGlobalFilterColumnVisiblitySelector is false and any of the following are true:

    • tableSpec.columnSpecs[] is empty (meaning that there are no columns available to filter against, hide or show)
    • all tableSpec.columnSpecs explicitly specify filterable: false (meaning that there are no targettable columns for filtering purposes)
    • pagedRecordsSource is currently set to "external" and externalPagingImplSupportsFilteringis not set true
  • In the case where the enablement of the global filter toolbar (via showGlobalRecordFilterToolbar) has been accepted solely because showGlobalFilterColumnVisiblitySelector is true (i.e. filtering is actually impossible for any of the reasons outlined above), the filtering-related elements of the toolbar (i.e. the filter text-field, the case-sensitivity checkbox and the filterable columns flagset) will be hidden (regardless of whether the corresponding properties enabling those features have been set true).

FlexTable defines two named "slots": "title" and "custom-filter-controls" into which child elements of the <uwc-flex-table> tag may be injected by supplying one of those slot names as the value of their "slot" attribute).

Where defined:

  • a child element referencing the "title" slot is shown immediately above the top lefthand corner of the table (as the leftmost element of the the global filter toolbar where shown, or on its own otherwise).

  • a child element referencing the "custom-filter-controls" slot is shown within the global filter toolbar as the rightmost element of the lefthand part of the filter toolbar (which includes everything except for the "match count" lozenge that is displayed a filter is in effect). Note that (unlike "title" slot content), an element targetting the "custom-filter-controls" slot is only ever displayed as part of the global filter toolbar.

    TIP: If you are planning to wrap any of your custom filter (e.g. <uwc-icon-button>) controls in <uwc-tooltip> ... </uwc-tooltip>, those wrapped controls will need to be placed inside a container (e.g. <div>) element with position: relative in order for the tooltips to be positioned correctly when those controls are hovered over - e.g:

    <div slot="custom-filter-controls" style="position: relative">
    <!-- your <uwc-tooltip>-wrapped controls here -->
    </div>

Use of these slots is demonstrated in the Live Demo examples with "slot content" in their names.

FlexTable provides the means to trigger the generation / download of a file containing a CSV representation of the subset of data records currently known to the FlexTable instance that match the global filter (if one is currently in effect).

This is achieved by invoking the method: triggerCSVDownload(downloadFilename: string) on the relevant FlexTable instance. A client application would typically include such a call within the event handler for a control declared either (a) externally to the <uwc-flex-table> tag, or (b) included as a sub-element of that tag and directed at FlexTable's "custom-filter-controls" slot (see Slots for title and custom global filter toolbar controls above). The latter (slot-based) approach, is demonstrated in Live Demo examples: Internal pagination + CSV download and Exernal pagination + CSV download.

The content of downloads provoked by the above comprises:

  • a column titles row: this contains the titles of currently visible data (i.e. tableSpec.columnSpecs[]-defined) columns as they appear on screen (note that record selector / record-level-actions columns are not represented in the CSV output)

  • a block of zero or more data rows: within this block, each data row contains the record's values for the fields associated with the columns corresponding to the titles in the column titles row (in that order), and the ordering of the data rows reflects the sort-order (if any) currently applied to the onscreen table.

    Note that (by design) the data rows block does not include rows for any of the following:

    • expandable record content
    • record-grouping annotation rows
    • summary cell rows

    Omission of the above ensures that all data rows conform to the same format (all data values for a given CSV "column" being sourced from the same record field), so facilitating analysis of the downloaded content using third party applications such as MS Excel, etc

In "external" paging mode, FlexTable only knows about the records on the currently displayed table page. Hence, in that mode (only) the scope of the record data included in a CSV download is necessarily limited to those records visible on the active table page. Unpaginated / internally paginated tables are not subject to this restriction (all records matching the global filter - if one is applied - being included in the download).

Regarding the record field from which CSV values for each column are sourced, FlexTable applies the following logic based on the properties defined by the tableSpec.columnSpecs[] element for each visible column:

  • if the column-spec defines defines a non-empty string for the (optional, string-valued) property: csvFieldName, then the field nominated by that property will be used as the source of values for that CSV column

    TIP: for column-specs defining columns where the display values are "dd/mm/YYYY" or "mm/dd/YYYY"-formatted strings, it's a good idea to specify the (optional) csvFieldName property with the name of a field containing the corresponding "YYYY-mm-dd"-formatted equivalents of those values. This will avoid problems with applications such as MS Excel attempting to interpret "date-like" values using the operating system's locale / regional-date-time-formatting settings on opening of a FlexTable-generated CSV file (the symptoms of which being that only the subset of values where the misidentified day-of-month component of a date value just happens to be in range 1-12 being recognized as dates in the case where the actual format of the date values in the CSV is (a) not YYYY-mm-dd, and (b) at odds with the locale / regional-date-time-formatting settings on the user's machine).

    NB: If you're retro-fitting the CSV download functionality described here into FlexTable(s) within an existing application, it's likely that such column-specs would already specify the optional sortFieldName property (referencing a suitable "YYYY-mm-dd"-formatted field in order that sorting records by that column results in a proper chronological ordering), so this would probably be a good way of identifying column-specs for which the optional csvFieldName should also be defined (with the same value).

  • otherwise, if the column-spec defines a non-empty string for the (optional, string-valued) property: filterFieldName (as might be the case if the same column-spec also specifies renderFieldValueAsHtml: true, for example), then the field nominated by that property is used (on the grounds that it's values should hopefully be free of any HTML formatting)

  • failing either of the above, field nominated by the (mandatory, string-valued) fieldName property is used

Pagination may be optionally be enabled by specifying a positive integer value (or equivalently, a string equivalent of the same) for the component's maxRecordsPerPage attribute/property. The type of pagination required is indicated via the component's pagedDataSource attribute/property, which takes a value of "internal" (the default) or "external":

  • "internal" indicates that pagination is handled entirely by the component itself.

    This is the easiest/least-effort way of managing pagination and appropriate for cases where:

    • the size (and upfront cost of retrieving) the full set of records (for potential client-side sorting/filtering) are not prohibitive, and...

    • a point-in-time-snapshot view of the record data is adequate to meet the end user's needs from a functional perspective.

  • "external" indicates that, whenever data for a page is needed, the component should simply fire arecordsForTablePageRequested event containing information about the data that is required.

    In this case, the expectation is that client-implemented event handler will be "listening" for these events, and on each invocation, that handler will:

    • obtain the (where applicable, filtered and/or sorted) data for either (a) the requested page, or (in the case where that's impossible - perhaps due to records having been deleted at source since the previous such event), (b) the page (if any) closest to the one requested

    • call FlexTable's showTablePage() method with appropriate parameters so that the component can updated it's UI accordingly

    (NB: For reference, the javascript source of the recordsForTablePageRequested listener used to support the various External pagination... examples in the Live Demo is provided under the "Code" tab above).

    Regarding the filtering/sorting aspects, FlexTable adapts both (a) it's UI and (b) the content of the recordsForTablePageRequested events that it fires to fit with the capabilities of the external paging implementation as expressed via the (boolean-valued) externalPagingImplSupportsFiltering and externalPagingImplSupportsSorting attributes/properties (both of which are defaulted false).

    A further property: multiColumnSortDisabled (which is actually honoured regardless of whether / what kind of pagination is enabled) makes it possible to describe the capabilities of an external paging implementation does support sorting, but only by a single field at a time.

    Though (necessarily) more demanding more of the client application, external pagination is likely to be the best option for cases where:

    • the underlying record data is too large (or slow to retrieve) to be fetched in its entirety "up front", and/or...

    • the underlying data source is one likely to experience frequent updates (edits to existing records, creation of new records, deletion of existing records) from other sources, and it's desirable that the user's view of the available records reflects the effects of any such updates each time the user performs filter/sort/pagination-related action

Where required, FlexTable can be configured such that:

  • all directly user-modifiable settings (i.e. those customizable by the user via FlexTable's UI) are automatically persisted to the browser's localStorage (under a nominated key) any time the user directly updates any of those settings

  • "on change" of it's tableSpec (providing (a) a localStorage key has been nominated, and (b) the effective subset of user-modifiable settings for restoration is non-empty), FlexTable will attempt to read the most recently-saved set of user-modifiable settings from localStorage, and (assuming such are found), restore/reapply the relevant subset of those (updating its UI accordingly)

Minimally, this behaviour may be enabled simply by specifying a non-empty string value for (optional, string-valued) tableSpec property: userSettingsLocalStorageKey, since by default, FlexTable assumes that ALL saved user-modifiable settings are eligible for restoration.

Where necessary, this may be customized by additionally providing an array containing zero or more of the following setting-type identifiers as the value for (optional, array-of-strings-valued) tableSpec property: restorableUserModifiableSettingTypes:

  • "columnConfig": the order, visibility and width of the table's columns (these are grouped together for settings restoration purposes due to the high degree of interdependence between them - and the resultant complexity involved in attempting to provide fine-grained control over each individually).

    Note that the restorability of these aspects implicitly depends on there having been no changes to the number or fieldName(s) defined by tableSpec.columnSpecs[] since the settings were saved.

    In the case where such changes are detected FlexTable simply refrains from attempting to restore these aspects (noting the reason for this via a warning message logged to the console). This has no impact on the restoration of any other applicable settings types from the stored settings.

  • "globalFilterText": the filter value for the global filter toolbar's filter text field (as per top-level property: globalRecordFilterValue) - regardless of whether the filter toolbar was/is now actually shown (as per top-level property: showGlobalRecordFilterToolbar)
  • "globalFilterCaseSensitivity": the state of the global filter's case-sensitivity option (as per top-level property: globalRecordFilterCaseSensitive) - regardless of whether the checkbox for that was/is now actually shown in the filter toolbar (as per top-level property: showGlobalFilterCaseSensitivityControl)
  • "globalFilterTargetColumns": the state of the global filter toolbar's "Filterable Columns" flagset - regardlessof whether that control was/is now actually shown in the filter toolbar - as per top-level property: showGlobalFilterColumnSelector)
  • "maxRecordsPerPage": the effective value of top-level property: maxRecordsPerPage (shown as the selected value in the pagination toolbar's "Rows per page" dropdown) - regardless of whether the pagination toolbar was/is now actually shown)
  • "sortSpec": the effective sort order (if any) for the record set (as per top-level property: sortSpec)

The component includes comprehensive keyboard support for actions such as:

  • navigating / operating the controls within the global filter toolbar (where enabled - see above)

  • navigating (and invoking) the "sort-direction" controls within table header cells (Tab/Shift+Tab move between the controls; Space/Shift+Space cycle forwards/backwards through the applicable sort options for the active sort-direction control; Backspace/Delete resets the sort state for that column and any subsequently sorted columns to unsorted)

  • navigating the data records (ArrowUp/ArrowDown move forward/backwards one record at a time, wrapping around to the first/last record where appropriate; Home/End keys jump to the first/final record respectively)

  • navigating (and invoking) the actions for a given record (Tab/Shift+Tab move between the actions;

    Enter invokes an action; Space opens/closes the actions menu; ArrowUp/ArrrowDownand Home/End keys navigate the action menu items, Space/Enter invoke those actions)

The default appearance (which includes a table border and horizontal / vertical gridlines, but no visible record selector column) optimises compactness (making it suitable for displaying relatively large amounts of data), but this is easily customizable via combination of dynamically-updatable properties and CSS variables.

It is possible to enable scrolling of just the data records without the table headers moving by the addition of the fixedHeaders property.

In order for this to make sense, a height needs to be set on the table body. This is achieved by setting the css variable

--uwc-flex-table--body-height. This defaults to 200px.

Features unsupported in this early version, but which are on the roadmap for this component include:

  • lazy rendering (to facilitate the browsing of / optimise the rendering of large record sets)
  • built in support for rendering field values as components (e.g. Checkbox or Switch for boolean values, Amount Field for numeric values, etc) instead of text
  • options to support the delegation of the rendering / filter-matching / sort-comparision of sets of component field values to externally-defined web components / objects

Features that we're not planning to support include:

  • in-table editing of data records

Our components have been designed to comply with the WCAG 2.1 AA accessibility guidelines. For more information about how our design system complies with color-related accessibility guidelines, see Foundations > Colors > Accessibility.

Related components: