uwc-flex-table code examples, properties, methods, events and Live Demo.

Here is an example of using the Flex Table in a plain html page:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link href="https://developer.temenos.com/uux/base.css" rel="stylesheet" />
<script src="https://developer.temenos.com/uux/unified-ux-web.min.js"></script>
</head>
<body>
<uwc-flex-table></uwc-flex-table>
</body>
<script>
const recordData = [
{
firstName: "Sterling",
lastName: "Archer",
actor: "Harry Jon Benjamin",
tvSeries: "Archer"
},
{
firstName: "Malory",
lastName: "Archer",
actor: "Jessica Walter",
tvSeries: "Archer"
},
{
firstName: "Lana",
lastName: "Kane",
actor: "Aisha Tyler",
tvSeries: "Archer"
},
{
firstName: "Cheryl",
lastName: "Tunt",
actor: "Judy Greer",
tvSeries: "Archer"
},
{
firstName: "Algernop",
lastName: "Krieger",
actor: "Matt \"Lucky\" Yates",
tvSeries: "Archer"
},
{
firstName: "Pam",
lastName: "Poovey",
actor: "Amber Nash",
tvSeries: "Archer"
},
{
firstName: "Cyril",
lastName: "Figgis",
actor: "Chris Parnell",
tvSeries: "Archer"
},
{
firstName: "Ray",
lastName: "Gillette",
actor: "Adam Brooks Reed",
tvSeries: "Archer"
},
{
firstName: "Philip",
lastName: "Fry",
actor: "Billy West",
tvSeries: "Futurama"
},
{
firstName: "Leela",
lastName: "Turanga",
actor: "Katy Sagal",
tvSeries: "Futurama"
},
{
firstName: "Bender",
lastName: "Rodríguez",
actor: "John DiMaggio",
tvSeries: "Futurama"
},
{
firstName: "Hermes",
lastName: "Conrad",
actor: "Phil Lamar",
tvSeries: "Futurama"
},
{
firstName: "Hubert",
lastName: "Farnsworth",
actor: "Billy West",
tvSeries: "Futurama"
},
{
firstName: "John",
lastName: "Zoidberg",
actor: "Billy West",
tvSeries: "Futurama"
}
];
const tableSpec = {
columnSpecs: [
{
label: "First Name",
fieldName: "firstName",
horizontalAlignment: "end"
},
{
label: "Last Name",
fieldName: "lastName",
},
{
label: "Actor/Voice",
fieldName: "actor",
//cssWidthValue: "200px"
//horizontalAlignment: "center"
},
{
label: "TV series",
fieldName: "tvSeries",
horizontalAlignment: "center",
renderFieldValueAsAction: true
}
],
recordLevelActionSpecs: [
{
actionId: "viewProfile",
title: "View profile",
icon: "account_circle",
//iconColorOrCSSVarname: "--uwc-status-warn-dark",
presentationStyle: "icon"
},
{
actionId: "edit",
title: "Edit",
icon: "edit",
presentationStyle: "link"
},
{
actionId: "sendFlowers",
title: "Send flowers",
presentationStyle: "menu"
},
{
actionId: "sendDinnerInvitation",
title: "Invite to dinner",
presentationStyle: "menu"
},
{
actionId: "sendMarriageProposal",
title: "Propose marriage",
presentationStyle: "menu"
}
],
dataRecords: recordData
};
document.querySelector("uwc-flex-table").tableSpec = tableSpec;
</script>
</html>

For reference, here's the JavaScript source of the genCharismaIconSpec() (and supporting) function(s) referenced as the value of the (optional) dataCellIconSpecGenerator property in the tableSpec.columnSpecs[] element for the "charisma" column in the With "charisma" values rendered as icons and With "charisma" values rendered as (field-level-action) icon buttons examples in the Live Demo below:

/**
* Copied verbatim from demos\flex-table\index.html, this function is referenced as the value of the (optional)
* dataCellIconSpecGenerator property in the tableSpec.columnSpecs[] element for the "charisma" column for the
* 'With "charisma" values rendered as icons' and 'With "charisma" values rendered as (field-level-action) icon buttons'
* examples.
*/
function genCharismaIconSpec(dataRecord) {
const { charisma } = dataRecord;
if (charisma >= 90) return {
icon: "local_fire_department",
iconColorOrCSSVarname: "yellow",
tooltip: genEffectiveIconTooltip({ lookupKey: "table.charismaIconTooltips.incandescent", defaultValue: "Incandescent !"}),
sortKey: genEffectiveIconSortKey("Z")
};
if (charisma >= 80) return {
icon: "local_fire_department",
iconColorOrCSSVarname: "red",
tooltip: genEffectiveIconTooltip({ lookupKey: "table.charismaIconTooltips.prettyDamnHot", defaultValue: "Pretty damn hot !" }),
sortKey: genEffectiveIconSortKey("Y")
};
if (charisma >= 70) return {
icon: "local_fire_department",
iconColorOrCSSVarname: "orange",
tooltip: genEffectiveIconTooltip({ lookupKey: "table.charismaIconTooltips.smouldering", defaultValue: "Smouldering" }),
sortKey: genEffectiveIconSortKey("X")
};
if (charisma >= 60) return {
icon: "flourescent",
iconColorOrCSSVarname: "greenyellow",
tooltip: genEffectiveIconTooltip({ lookupKey: "table.charismaIconTooltips.lightWithoutHeat", defaultValue: "Somewhat witty" }),
sortKey: genEffectiveIconSortKey("W")
};
if (charisma >= 50) return {
icon: "emoji_objects",
iconColorOrCSSVarname: "--uwc-status-warn-primary",
tooltip: genEffectiveIconTooltip({ lookupKey: "table.charismaIconTooltips.lowWattageBulb", defaultValue: "Not entirely dim" }),
sortKey: genEffectiveIconSortKey("V")
};
if (charisma >= 40) return {
icon: "light",
iconColorOrCSSVarname: "--uwc-status-warn-dark",
tooltip: genEffectiveIconTooltip({ lookupKey: "table.charismaIconTooltips.shadedLowWattageLightBulb", defaultValue: "Generally dull" }),
sortKey: genEffectiveIconSortKey("U")
}
if (charisma >= 30) return {
icon: "sentiment_neutral",
tooltip: genEffectiveIconTooltip({ lookupKey: "table.charismaIconTooltips.charismaVacuum", defaultValue: "Charisma vaccum" }),
sortKey: genEffectiveIconSortKey("T")
}
if (charisma >= 10) return {
icon: "question_mark",
tooltip: genEffectiveIconTooltip({ lookupKey: "table.charismaIconTooltips.nonEntity", defaultValue: "Non entity" }),
sortKey: genEffectiveIconSortKey("S")
}
if (charisma >= 10) return {
icon: "trending_down",
tooltip: genEffectiveIconTooltip({ lookupKey: "table.charismaIconTooltips.barelyAlive", defaultValue: "Barely alive" }),
sortKey: genEffectiveIconSortKey("R")
}
return {
icon: "warning",
iconColorOrCSSVarname: "blue",
tooltip: genEffectiveIconTooltip({ lookupKey: "table.charismaIconTooltips.totallyBoring", defaultValue: "Totally boring" }),
sortKey: genEffectiveIconSortKey("Q")
}
}; // genCharismaIconSpec()
/**
* This (functionally redundant) supporting function is included to facilitate simple copy/paste updates of
* genCharismaIconSpec() (below) from demos\flex-table\index.html :-)
*/
function genEffectiveIconSortKey(sortKey) {
return sortKey;
} // genEffectiveIconSortKey()
/**
* This supporting function is included to facilitate simple copy/past updates of genCharismaIconSpec()
* (below) from demos\flex-table\index.html :-)
*
* The implementation here reflects the fact that (unlike in demos\flex-table\index.html), we're *not* currently
* including any translation examples in our Live Demo examples.
*/
function genEffectiveIconTooltip(tooltip) {
if (tooltip) {
switch(typeof tooltip) {
case "object":
const { defaultValue } = tooltip;
return (defaultValue && (typeof defaultValue === "string")) ? defaultValue : undefined;
case "string":
return tooltip;
}
}
return undefined;
} // genEffectiveIconTooltip()

For reference, here's the JavaScript source of the genTextCellColorSpecBasedOnCrucialityOfCharacterToSeries() function referenced as the value of the textCellColorSpecGenerator property in the tableSpec.columnSpecs[] elements for the First Name and Last Name columns in the With color highlighting of First / Last Name values for main / important characters example in the Live Demo below:

/**
* Copied verbatim from demos\flex-table\index.html, this function is referenced as the value of the (optional)
* textCellColorSpecGenerator property in the tableSpec.columnSpecs[] elements for the "First Name" and "Last Name"
* columns for the "With color highlighting of First / Last Name values for main / important characters" example.
*/
function genTextCellColorSpecBasedOnCrucialityOfCharacterToSeries(dataRecord) {
const { crucialityToTvSeries } = dataRecord;
let result = undefined;
if (typeof crucialityToTvSeries === "number") {
let cellBackgroundColorOrCSSVarname = undefined;
switch(crucialityToTvSeries) {
case 1:
cellBackgroundColorOrCSSVarname = "yellow";
break;
case 2:
cellBackgroundColorOrCSSVarname = "orange";
break;
}
if (cellBackgroundColorOrCSSVarname) {
result = {
textColorOrCSSVarname: "blue",
cellBackgroundColorOrCSSVarname: cellBackgroundColorOrCSSVarname
};
}
}
return result;
}; // genTextCellColorSpecBasedOnCrucialityOfCharacterToSeries()

For reference, here's the JavaScript source of the genDefaultRowColorsBasedOnCrucialityOfCharacterToSeries() function referenced as the value of the tableSpec.recordRowColorGeneratorFunction in the With color highlighting of record rows for main / important characters example in the Live Demo below:

function genDefaultRowColorsBasedOnCrucialityOfCharacterToSeries(dataRecord) {
const { crucialityToTvSeries } = dataRecord;
let result = undefined;
if (typeof crucialityToTvSeries === "number") {
let defaultRowBackgroundColorOrCSSVarname = undefined;
switch(crucialityToTvSeries) {
case 1:
defaultRowBackgroundColorOrCSSVarname = "yellow";
break;
case 2:
defaultRowBackgroundColorOrCSSVarname = "orange";
break;
}
if (defaultRowBackgroundColorOrCSSVarname) {
result = {
defaultRowForegroundColorOrCSSVarname: "blue",
defaultRowBackgroundColorOrCSSVarname: defaultRowBackgroundColorOrCSSVarname
};
}
}
return result;
}; // genDefaultRowColorsBasedOnCrucialityOfCharacterToSeries()

For reference, here's the JavaScript source of the getRecordGroupAnnotations() function (and supporting constants) supplied as the value for tableSpec.recordGroupAnnotationFunction in the Pre-sorted by "Updated" (asc) with record grouping example in the Live Demo below:

const
LOG_MSG_PREFIX = "[flex-table/examples.js] ",
MONTH_HAMES = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
/**
* The tableSpec used for the "Pre-sorted by "Updated"..." Live Demo examples reference this function as the
* the value of its recordGroupAnnotationFunction property.
*
* This implementation returns "<monthName> <year>" annotations (based on record values for the
* "lastUpdatedYYYYMMDD" field) if (and only if) the supplied sortSpec indicates that "lastUpdatedYYYYMMDD"
* is the primary (i.e. most significant) active sort field.
*
* @param renderableDataRecords an array of record objects representing the subset of data records
* for which any applicable record group annotations are requested
*
* @param sortSpec an object functioning as an ordered map of (active) sort directions
* ("unsorted" | "ascending" | "descending") keyed by effective sort field
* name (i.e. sortFieldName | fieldName as defined by the column spec for
* each sorted column).
*
* @returns a (possibly empty) Map whose (number-typed) keys represent the indices at which each record group
* annotation is to be inserted, and whose (string-typed) values define the text content of those
* annotations.
*/
function getRecordGroupAnnotations(renderableDataRecords, sortSpec) {
console.log(
`${LOG_MSG_PREFIX}getRecordGroupAnnotations():`,
`\n- renderableDataRecords`, renderableDataRecords,
`\n- sortSpec: `, sortSpec
);
const renderableRecordCount = (renderableDataRecords instanceof Array) ? renderableDataRecords.length : 0;
let result = new Map();
if (renderableRecordCount && (typeof sortSpec === "object")) {
const
primarySortFieldName = sortSpec ? Object.keys(sortSpec)[0] : undefined,
renderableRecordCount = renderableDataRecords.length;
if (primarySortFieldName === "lastUpdatedYYYYMMDD") {
let
prevLastUpdatedYYYYMMDDValue = undefined,
prevAnnotationText = undefined;
for(let i = 0; i < renderableRecordCount; ++i) {
const currLastUpdatedYYYYMMDDValue = renderableDataRecords[i].lastUpdatedYYYYMMDD || "";
if ((! currLastUpdatedYYYYMMDDValue) || (currLastUpdatedYYYYMMDDValue === prevLastUpdatedYYYYMMDDValue))
continue;
const lastUpdatedDateParts = currLastUpdatedYYYYMMDDValue.split("-");
if (lastUpdatedDateParts.length != 3)
continue;
const
year = lastUpdatedDateParts[0] || "",
monthNumber = parseInt(lastUpdatedDateParts[1]),
monthName = (isNaN(monthNumber) ? undefined : MONTH_HAMES[monthNumber - 1]) || "",
annotationText = (monthName && year) ? `${monthName} ${year}` : "";
if (annotationText != prevAnnotationText) {
result.set(i, annotationText);
prevLastUpdatedYYYYMMDDValue = currLastUpdatedYYYYMMDDValue;
prevAnnotationText = annotationText;
}
}
}
}
console.log("> returning: ", result);
return result;
} // getRecordGroupAnnotations()

For reference, here's JavaScript source of the handleRecordsForTablePageRequested() function (and supporting constants) that's registered as the listener for "recordsForTablePageRequested" events within the various External pagination... examples within the Live Demo below:

const LOG_MSG_PREFIX = "[flex-table/examples.js] ";
function handleRecordsForTablePageRequested(e) {
const { detail, target: flexTableElem } = e;
console.log(`${LOG_MSG_PREFIX}recordsForTablePageRequested(e): e.detail: `, detail);
if (! detail)
return;
let
offsetOfFirstRecordForReturnedPage = -1,
recordsForPage = [],
availableRecordCount = 0;
if (dataRecords.length) {
const { filterSpec, sortSpec } = detail;
let filteredRecords = [ ...dataRecords ];
if (filterSpec) {
const
{ caseSensitive, filterValue, targetFieldNames } = filterSpec,
targetFieldNameCount = targetFieldNames ? targetFieldNames.length : 0;
if (filterValue) {
const caseAdaptedFilterValue = caseSensitive ? filterValue : filterValue.toLocaleLowerCase();
filteredRecords = filteredRecords.filter(dataRecord => {
for(let iFieldName = 0; iFieldName < targetFieldNameCount; ++iFieldName) {
const filterableStringForRecordAndField =
flexTableElem.getFilterableStringForDataRecordAndFieldName(dataRecord, targetFieldNames[iFieldName]);
if (filterableStringForRecordAndField == null)
continue;
if (filterableStringForRecordAndField.length < caseAdaptedFilterValue.length)
continue;
const caseAdaptedFieldValueString = caseSensitive
? filterableStringForRecordAndField
: filterableStringForRecordAndField.toLocaleLowerCase();
if (caseAdaptedFieldValueString.indexOf(caseAdaptedFilterValue) >= 0)
return true;
}
return false;
});
}
} // if (filterSpec)
const filteredRecordsSorted = filteredRecords;
if (filteredRecords.length && sortSpec && Object.keys(sortSpec).length) {
const fieldComparatorFunctionsArray = [];
Object.entries(sortSpec).forEach(([fieldName, columnSortDirection]) => {
let sortDirectionMultiplier = 0;
switch(columnSortDirection) {
case "ascending":
sortDirectionMultiplier = 1;
break;
case "descending":
sortDirectionMultiplier = -1;
break;
}
const dataCellIconSpecGenerator = flexTableElem.getIconSpecGeneratorFunctionForFieldName(fieldName);
const compareFn = dataCellIconSpecGenerator
? (dataRecord1, dataRecord2) => {
const
sortKey1 = flexTableElem.extractDataCellIconSortKey(dataCellIconSpecGenerator(dataRecord1)),
sortKey2 = flexTableElem.extractDataCellIconSortKey(dataCellIconSpecGenerator(dataRecord2));
return sortKey1.localeCompare(sortKey2) * sortDirectionMultiplier;
}
: (dataRecord1, dataRecord2) => {
const
fieldValue1 = dataRecord1[fieldName] || "",
fieldValue2 = dataRecord2[fieldName] || "";
if (fieldValue1 > fieldValue2)
return 1 * sortDirectionMultiplier;
if (fieldValue1 < fieldValue2)
return -1 * sortDirectionMultiplier;
return 0;
};
fieldComparatorFunctionsArray.push(compareFn);
});
const numFieldComparatorFunctions = fieldComparatorFunctionsArray.length;
const recordComparatorFn = (dataRecord1, dataRecord2) => {
for(let i = 0; i < numFieldComparatorFunctions; ++i) {
const compareResult = fieldComparatorFunctionsArray[i](dataRecord1, dataRecord2);
if (compareResult)
return compareResult;
}
return 0;
}
filteredRecordsSorted.sort(recordComparatorFn);
} // if filtering left us with some matching records and event.detail included a non-empty sortSpec
availableRecordCount = filteredRecordsSorted.length;
if (availableRecordCount) {
const
{ offsetOfFirstRequestedRecord, maxRecordsPerPage } = detail,
indexOfFinalAvailableRecord = availableRecordCount - 1;
offsetOfFirstRecordForReturnedPage = (offsetOfFirstRequestedRecord <= indexOfFinalAvailableRecord)
? offsetOfFirstRequestedRecord
: Math.floor(indexOfFinalAvailableRecord / maxRecordsPerPage) * maxRecordsPerPage;
recordsForPage = filteredRecordsSorted.slice(offsetOfFirstRecordForReturnedPage, offsetOfFirstRecordForReturnedPage + maxRecordsPerPage);
}
} // if (dataRecords.length)
const simulateRecordCountUnavailable = flexTableElem.classList.contains(CSSClassNames.NO_AVAILABLE_RECORD_COUNT);
setTimeout(
() => {
flexTableElem.showTablePage(
recordsForPage,
offsetOfFirstRecordForReturnedPage,
simulateRecordCountUnavailable ? undefined : availableRecordCount
)
},
500
);
} // handleRecordsForTablePageRequested()

This example shows how a FlexTable configured to include expandable/collapsible record grouping annotation rows (for a sort field that's also nominated as the primary "base sort" field in the table's sortSpec) might be augmented to include additional per-record-grouping "summary" rows containing aggregate values based on the data in each such grouping.

The approach demonstrated here involves pre-sorting the dataRecords and then injecting additional "pseudo" dataRecords to represent the grouping rows (based on tracking aggregate total values for records sharing a common value for the relevant sort-key during a traversal of the original data records).

A secondary base sort-key (isGroupingSummaryRow) is added to ensure that the rows for the injected "pseudo" grouping summary records always appear as the final row within each grouping (regardless of any column-sorts that the user may choose to apply on top of the base sort order).

Limitations

Be aware that this approach produces less-than-ideal results when FlexTable's "global record filter" functionality is enabled in that the (pre-generated) grouping summary records are included/excluded from filtered results based solely on whether or not they match the filter. As such, in the cases where the subset of records matched by the filter includes both "real" records and one or more "pseudo" group summary records, the totals reported by the later will have no relation to the former.

The approach is therefore best suited to cases where the "global record filter" functionality is not required.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>FlexTable: record-grouping-total-rows</title>
<link href="https://developer.temenos.com/uux/base.css" rel="stylesheet" />
<script src="https://developer.temenos.com/uux/unified-ux-web.min.js"></script>
<style>
uwc-flex-table {
--uwc-flex-table-record-group-anno-background-color: whitesmoke;
}
</style>
<script>
/*
* Our "raw" data records (as might be received from an API call, for instance)
*/
const dataRecords = [
{
category: "X",
item: "X-001",
a: 2.8,
b: 3.6,
c: 2
},
{
category: "X",
item: "X-002",
a: 2.4,
b: 3.1,
c: 4
},
{
category: "X",
item: "X-003",
a: 1.9,
b: 3.12,
c: 3
},
{
category: "Y",
item: "Y-001",
a: 2.8,
b: 3.6,
c: 2
},
{
category: "Y",
item: "Y-002",
a: 2.4,
b: 3.1,
c: 4
},
{
category: "Y",
item: "Y-003",
a: 1.9,
b: 3.12,
c: 3
},
{
category: "Y",
item: "Y-004",
a: 1.8,
b: 3.13,
c: 4
},
{
category: "Z",
item: "Z-001",
a: 2.8,
b: 3.6,
c: 2
},
{
category: "Z",
item: "Z-002",
a: 2.4,
b: 3.1,
c: 4
}
];
/*
* Constants for field-names *added* to each element of dataRecords[] by preprocessDataRecords() below
*/
const ExtendedRecordFieldNames = Object.freeze({
/*
* The sortFieldName for the columnSpec with fieldName: "category".
* Referenced as the *primary* "base sort key" in our sortSpec.
*/
SORTABLE_CATEGORY: "sortableCategory",
/*
* This is set false for data records, and true for grouping summary records.
*
* Referenced as the *secondary* "base sort key" in our sortSpec (to ensure that table rows for category grouping summary rows
* ALWAYS come AFTER the data records for that category *regardless* of any additional column-sorts may have been applied by
* the user (true > false))
*/
IS_GROUPING_SUMMARY_ROW: "isGroupingSummaryRow"
});
const columnSpecs = [
{
label: "Item",
fieldName: "item"
},
{
label: "Category",
fieldName: "category",
sortFieldName: ExtendedRecordFieldNames.SORTABLE_CATEGORY
},
{
label: "A",
fieldName: "a",
horizontalAlignment: "end",
renderFieldValueAsHtml: true,
},
{
label: "B",
fieldName: "b",
horizontalAlignment: "end",
renderFieldValueAsHtml: true
},
{
label: "C",
fieldName: "c",
horizontalAlignment: "end",
renderFieldValueAsHtml: true
}
];
/*
* Preprocesses dataRecords[], ensuring this is sorted in ascending order of category, augmenting the original records with additional fields
* (per ExtendedRecordFieldNames above), tracking totals values for summarizable fields for the current category / remembering insert
* position + details for each insertable (per category) group summary row on change of category, and finally (on completion of the above)
* inserting the per-category group summary records at the appropriate indices.
*/
function preprocessDataRecords() {
const
iFinalDataRecord = dataRecords.length - 1,
/*
* A map of group summary record objects keyed by the index at which each needs to be inserted (relative
* to dataRecords[] "as is" - i.e. *prior* to injection of any group summary records)
*/
groupSummaryRecordByInsertIndex = new Map(),
/*
* Used below to track total values for the grouping category that we're currently processing.
*/
fieldTotalsForCurrentCategory = {
a: 0,
b: 0,
c: 0
},
/*
* Local helper function to increment the fields in fieldTotalsForCurrentCategory with the corresponding
* values from a given dataRecord[] object.
*/
incrementFieldTotalsForCurrentCategory = (a, b, c) => {
fieldTotalsForCurrentCategory.a += a;
fieldTotalsForCurrentCategory.b += b;
fieldTotalsForCurrentCategory.c += c;
},
/*
* Local helper function to zeroize the fields in fieldTotalsForCurrentCategory.
*/
zeroizeFieldTotalsForCurrentCategory = () => {
fieldTotalsForCurrentCategory.a =
fieldTotalsForCurrentCategory.b =
fieldTotalsForCurrentCategory.c = 0;
},
/*
* Local helper function to generate the html for a supplied (grouping summary field) label and value
*/
genGroupSummaryValueHtml = (label, value) => `<span style="color: darkblue"; font-weight: 500">${label}: </span>${value.toFixed(2)}`
let
currentCategory = undefined,
recordCountForCurrentCategory = 0;
// Pre-sort our dataRecords into ascending order of category (our record grouping field)
dataRecords.sort((a, b) => a.category.localeCompare(b.category));
/*
* Iterate our (sorted) dataRecords, on each iteration:
*
* - preprocessing the original data record:
* - convert numeric values to fixed decimal format (2 decimal places)
* - extend the original record with additional fields (named / described in ExtendedRecordFieldNames above)
*
* - tracking totals values for category group summarizable fields / adding entries to our groupSummaryRecordByInsertIndex map
* (one for each grouping summary "pseudo" record that subsequently we'll subsequently need to inject into dataRecords[])
* as appropriate
*/
for(let iDataRecord = 0; iDataRecord <= iFinalDataRecord; ++iDataRecord) {
const
dataRecord = dataRecords[iDataRecord],
isFinalDataRecord = (iDataRecord === iFinalDataRecord),
{ category, a, b, c } = dataRecord,
isCategoryChange = (!!currentCategory && (category !== currentCategory));
/*
* Augment the original data record with additional fields / convert numeric values to fixed decimal (2 dp)...
*/
Object.assign(
dataRecord,
{
[ExtendedRecordFieldNames.SORTABLE_CATEGORY]: category,
[ExtendedRecordFieldNames.IS_GROUPING_SUMMARY_ROW]: false,
a: a.toFixed(2),
b: b.toFixed(2),
c: c.toFixed(2),
}
);
if (isCategoryChange || isFinalDataRecord) {
let groupSummaryRowInsertIndex = iDataRecord;
if (isFinalDataRecord) {
if (! isCategoryChange) {
/*
* Either (a) the category of our final record matches that of the previous dataRecord, OR
* b) there was *no* previous record (i.e. dataRecord is the *sole* element of dataRecords[])
*/
++recordCountForCurrentCategory;
}
else {
/*
* The category of our final record differs from that of the previous record.
*
* 1. Add an entry to groupSummaryRecordByInsertIndex representing the insert position / group summary record for
* the category of the *previous* record...
*/
groupSummaryRecordByInsertIndex.set(
groupSummaryRowInsertIndex,
{
sortableCategory: currentCategory || category,
[ExtendedRecordFieldsNames.IS_GROUPING_SUMMARY_ROW]: true,
a: genGroupSummaryValueHtml("Total", fieldTotalsForCurrentCategory.a),
b: genGroupSummaryValueHtml("Total", fieldTotalsForCurrentCategory.b),
c: genGroupSummaryValueHtml("Mean", fieldTotalsForCurrentCategory.c / recordCountForCurrentCategory)
}
);
/*
* 2. Set things up such that subsequent code will add a (further) groupSummaryRecordByInsertIndex entry for
* representing the insert position / group summary record for category of our *final* dataRecord (which is
* the *sole* record with that category)
*/
currentCategory = category;
recordCountForCurrentCategory = 1;
++groupSummaryRowInsertIndex;
zeroizeFieldTotalsForCurrentCategory();
} // else [! isCategoryChange]
incrementFieldTotalsForCurrentCategory(a, b, c);
} // if (isFinalDataRecord)
groupSummaryRecordByInsertIndex.set(
groupSummaryRowInsertIndex,
{
sortableCategory: currentCategory || category,
[ExtendedRecordFieldNames.IS_GROUPING_SUMMARY_ROW]: true,
a: genGroupSummaryValueHtml("Total", fieldTotalsForCurrentCategory.a),
b: genGroupSummaryValueHtml("Total", fieldTotalsForCurrentCategory.b),
c: genGroupSummaryValueHtml("Mean", fieldTotalsForCurrentCategory.c / recordCountForCurrentCategory)
}
);
if (isFinalDataRecord)
break; // for each dataRecords[] element
recordCountForCurrentCategory = 0;
zeroizeFieldTotalsForCurrentCategory();
} // if (isCategoryChange || isFinalDataRecord)
incrementFieldTotalsForCurrentCategory(a, b, c);
currentCategory = category;
++recordCountForCurrentCategory;
} // for each dataRecords[] element
const summaryRecordMapEntriesArray = Array.from(groupSummaryRecordByInsertIndex.entries());
/*
* Finally, inject our group-summary records (processing summaryRecordMapEntriesArray in *reverse* order
* so that, on each iteration, the insertIndex that stored (above) as the key of the current entry *remains*
* correct regardless of any insertions performed on preceding iterations).
*/
for(let iEntry = summaryRecordMapEntriesArray.length - 1; iEntry >= 0; --iEntry) {
const [ insertIndex, groupSummaryRecord ] = summaryRecordMapEntriesArray[iEntry];
dataRecords.splice(insertIndex, 0, groupSummaryRecord);
}
return dataRecords;
} // preprocessDataRecords()
addEventListener("DOMContentLoaded", () => {
const uwcFlexTableElem = document.querySelector("uwc-flex-table");
uwcFlexTableElem.tableSpec = {
primaryKeyFieldName: "item",
columnSpecs: columnSpecs,
dataRecords: preprocessDataRecords(),
recordGroupAnnotationRowOptions: {
annoFieldNameByPrimarySortFieldName: {
[ExtendedRecordFieldNames.SORTABLE_CATEGORY]: ExtendedRecordFieldNames.SORTABLE_CATEGORY
},
expandableCollapsible: true
}
};
uwcFlexTableElem.sortSpec = {
// This is our primary "base sort" key
[`${ExtendedRecordFieldNames.SORTABLE_CATEGORY}!`]: "ascending",
// This secondary "base sort" key ensures that rows for category group summary records always appear AFTER those for data records in that category
[`${ExtendedRecordFieldNames.IS_GROUPING_SUMMARY_ROW}!`]: "ascending"
};
uwcFlexTableElem.recordSelectionMode = "single";
//uwcFlexTableElem.showGlobalRecordFilterToolbar = true;
});
</script>
</head>
<body>
<uwc-flex-table _debug></uwc-flex-table>
</body>
</body>