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
- Record selection
- Record actions
- Rendering field values as icons or icon buttons
- Rendering field values as HTML
- Expandable record content
- Record-specific data cell colouring
- Record-specific row colouring
- Record-specific tooltips on data cells
- Sorting (via column headers)
- Sorting - enforcing a "base" sort order
- Column reordering / resizing
- Injection of "record-grouping annotation" rows
- Injection of record-set-level aggregate value / summary rows
- Global record filter toolbar
- Slots for title and custom filter toolbar controls
- CSV download
- Pagination
- Preservation and restoration of user-modifiable settings using localStorage
- Keyboard support
- Fixed header scrolling
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
.
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 setrecordSelectionMode
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
ornumber
) across all data-record objectswhere 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.
dataCellIconSpecGenerator
property.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 / alltableSpec.dataRecords[]
objects) providing the "expandable content" (if any) for that recorda function of the form:
(for FlexTable to call whenever it needs to [re]obtain the latest expandable content for a data record).[async]
(dataRecord): string | null | undefined | Promise<string | null | undefined>
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]
Note that this is the sole mandatory property - the remainder are all optional and (where applicable) defaultedexpandableContentSource
: the source from which FlexTable is to obtain the expandable content for a record (i.e. thestring
name of a record field, or a function - see above).[optional]
(This is used as the action icon's tooltip if the effective value ofactionTitleExpand
defines the title (default:"Show details"
) for the auto-generated "expand" action.expandCollapseActionPresentationStyle
is"icon"
, or as the action menu item's label if the effective value of that property is"menu"
)[optional]
(This is used as the action icon's tooltip if the effective value ofactionTitleCollapse
defines the title (defaults to:"Hide details"
) for the auto-generated "collapse" actionexpandCollapseActionPresentationStyle
is"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 optionalexpandCollapseActionPosition
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 optionalexpandCollapseActionPosition
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.
- where the effective value for optional property:
"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.
- where the effective value for optional property:
[optional]
whereExpandableContentFromFieldBlank
: relevant only ifexpandableContentSource
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 atoString()
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 ofnoContentAvailableMessage
(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 ofwhereExpandableContentFromFieldBlank
(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 atoString()
method or returns something other than a non-blank string from that methodexpandableContentSource
is afunction
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"
- a positive
[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 forcacheLifetimeMillisForFunctionSourcedExpandableContent
(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) tableSpec
property: 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 itstranslationProvider
)a
DataValueTooltipSpec
: a plain javascript object of the form described below, in which:contentTemplate
is either astring
orTranslatable
(as described above)cssStyleProperties
provides the means to override CSS properties:max-width
and/orwhite-space
on the<uwc-tooltip>
elements generated for this column.
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
totrue
.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 providingmultiColumnSortDisabled
has not been settrue
), 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
orDelete
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 tounsorted
.All data columns are considered sortable by default, but this can overridden on a per-column basis as required by specifying
sortable: false
in relevanttableSpec.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.recordGroupAnnotationFunction
property.
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:
...and the semantics of those properties (all of which are optional) are follows:
expandableCollapsible
: specifytrue
if the injected grouping rows are to be made expandable/collapsible (defaults tofalse
)includeRecordCountsIfUnpaginated
: specifytrue
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 tofalse
)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 bytableSpec.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
NB: For such definitions, FlexTable will assume (a) that the values to be aggregated are those for the record field identified by the parentstring
name of a supported aggregation/summary cell typecolumnSpec
'sfieldName
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 toNumber
"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 toNumber
"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 toNumber
), 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 applicabletableSpec.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 cellvalue: string
: the (possibly calculated) value (if any) for this custom summary cellinlineValueStyle: string
: the inline style (if any) to be applied to the value (if any) for this custom summary cellsummaryValueActionId: 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'sfieldName
)[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 ifsummarizableValueFieldName
- see above - is specified).[optional]
summaryValueActionId?: string | number
: Specifying a non-blankstring
/ or anumber
value for this property tells FlexTable, that where present (subject to either (a) top-level property:suppressSummaryValueActionsWhenGlobalFilterActive
beingfalse
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 adetail
object with properties as follows:summaryValueActionId
: the value defined by this (identically named) "summarization spec" propertysummaryValue
: 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 bytable.columnSpecs[]
, but ordered to reflect any column-reordering that may have been performed by the user.dataRecords
: an array containing that subset oftableSpec.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 specifyfilterable: 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-valuedshowGlobalFilterColumnVisiblitySelector
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 specifyingfalse
as the value for (optional, boolean-valued) property:visibleByDefault
ontableSpec.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) pressesCtrl+Backspace
(which clears the field), or (c) leaves the filter value text-field (e.g. by pressingTab
).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 relevanttableSpec.columnSpecs[]
instance(s).
FlexTable will reject/override any attempt to set
showGlobalRecordFilterToolbar
true made whileshowGlobalFilterColumnVisiblitySelector
isfalse
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 specifyfilterable: false
(meaning that there are no targettable columns for filtering purposes) pagedRecordsSource
is currently set to"external"
andexternalPagingImplSupportsFiltering
is not settrue
In the case where the enablement of the global filter toolbar (via
showGlobalRecordFilterToolbar
) has been accepted solely becauseshowGlobalFilterColumnVisiblitySelector
istrue
(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 settrue
).
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
TIP: If you are planning to wrap any of your custom filter (e.g."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.<uwc-icon-button>
) controls in<uwc-tooltip> ... </uwc-tooltip>
, those wrapped controls will need to be placed inside a container (e.g.<div>
) element withposition: 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:
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
, then the field nominated by that property will be used as the source of values for that CSV columncsvFieldName
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 optionalcsvFieldName
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 specifiesrenderFieldValueAsHtml: 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
andexternalPagingImplSupportsSorting
attributes/properties (both of which are defaultedfalse
).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 bytableSpec.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 tounsorted
)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
/ArrrowDown
andHome
/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: