1044 lines
34 KiB
TypeScript
1044 lines
34 KiB
TypeScript
import React, { lazy, Suspense } from "react";
|
|
import BaseWidget, { WidgetState } from "../BaseWidget";
|
|
import { RenderModes, WidgetType } from "constants/WidgetConstants";
|
|
import { EventType } from "constants/ActionConstants";
|
|
import {
|
|
compare,
|
|
getDefaultColumnProperties,
|
|
getTableStyles,
|
|
renderCell,
|
|
renderDropdown,
|
|
renderActions,
|
|
sortTableFunction,
|
|
reorderColumns,
|
|
} from "components/designSystems/appsmith/TableComponent/TableUtilities";
|
|
import { getAllTableColumnKeys } from "components/designSystems/appsmith/TableComponent/TableHelpers";
|
|
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
|
import {
|
|
BASE_WIDGET_VALIDATION,
|
|
WidgetPropertyValidationType,
|
|
} from "utils/WidgetValidation";
|
|
import { TriggerPropertiesMap } from "utils/WidgetFactory";
|
|
import Skeleton from "components/utils/Skeleton";
|
|
import moment from "moment";
|
|
import { isNumber, isString, isUndefined, isEqual, xor, without } from "lodash";
|
|
import * as Sentry from "@sentry/react";
|
|
import { retryPromise } from "utils/AppsmithUtils";
|
|
import withMeta from "../MetaHOC";
|
|
import { getDynamicBindings } from "utils/DynamicBindingUtils";
|
|
import log from "loglevel";
|
|
import { ReactTableFilter } from "components/designSystems/appsmith/TableComponent/TableFilters";
|
|
import { TableWidgetProps } from "./TableWidgetConstants";
|
|
import derivedProperties from "./parseDerivedProperties";
|
|
|
|
import {
|
|
ColumnProperties,
|
|
CellLayoutProperties,
|
|
ReactTableColumnProps,
|
|
ColumnTypes,
|
|
Operator,
|
|
OperatorTypes,
|
|
CompactModeTypes,
|
|
CompactMode,
|
|
} from "components/designSystems/appsmith/TableComponent/Constants";
|
|
import tablePropertyPaneConfig from "./TablePropertyPaneConfig";
|
|
const ReactTableComponent = lazy(() =>
|
|
retryPromise(() =>
|
|
import("components/designSystems/appsmith/TableComponent"),
|
|
),
|
|
);
|
|
|
|
class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|
static getPropertyValidationMap(): WidgetPropertyValidationType {
|
|
return {
|
|
...BASE_WIDGET_VALIDATION,
|
|
tableData: VALIDATION_TYPES.TABLE_DATA,
|
|
nextPageKey: VALIDATION_TYPES.TEXT,
|
|
prevPageKey: VALIDATION_TYPES.TEXT,
|
|
label: VALIDATION_TYPES.TEXT,
|
|
searchText: VALIDATION_TYPES.TEXT,
|
|
defaultSearchText: VALIDATION_TYPES.TEXT,
|
|
defaultSelectedRow: VALIDATION_TYPES.DEFAULT_SELECTED_ROW,
|
|
pageSize: VALIDATION_TYPES.NUMBER,
|
|
};
|
|
}
|
|
|
|
static getPropertyPaneConfig() {
|
|
return tablePropertyPaneConfig;
|
|
}
|
|
|
|
static getMetaPropertiesMap(): Record<string, any> {
|
|
return {
|
|
pageNo: 1,
|
|
selectedRowIndex: undefined,
|
|
selectedRowIndices: undefined,
|
|
searchText: undefined,
|
|
// The following meta property is used for rendering the table.
|
|
filteredTableData: undefined,
|
|
filters: [],
|
|
hiddenColumns: [],
|
|
compactMode: CompactModeTypes.DEFAULT,
|
|
};
|
|
}
|
|
|
|
static getDerivedPropertiesMap() {
|
|
return {
|
|
selectedRow: `{{(()=>{${derivedProperties.getSelectedRow}})()}}`,
|
|
selectedRows: `{{(()=>{${derivedProperties.getSelectedRows}})()}}`,
|
|
pageSize: `{{(()=>{${derivedProperties.getPageSize}})()}}`,
|
|
triggerRowSelection: "{{!!this.onRowSelected}}",
|
|
};
|
|
}
|
|
|
|
static getDefaultPropertiesMap(): Record<string, string> {
|
|
return {
|
|
searchText: "defaultSearchText",
|
|
selectedRowIndex: "defaultSelectedRow",
|
|
selectedRowIndices: "defaultSelectedRow",
|
|
};
|
|
}
|
|
|
|
static getTriggerPropertyMap(): TriggerPropertiesMap {
|
|
return {
|
|
onRowSelected: true,
|
|
onPageChange: true,
|
|
onSearchTextChanged: true,
|
|
onPageSizeChange: true,
|
|
};
|
|
}
|
|
|
|
getPropertyValue = (value: any, index: number, preserveCase = false) => {
|
|
if (value && Array.isArray(value) && value[index]) {
|
|
return preserveCase
|
|
? value[index].toString()
|
|
: value[index].toString().toUpperCase();
|
|
} else if (value) {
|
|
return preserveCase ? value.toString() : value.toString().toUpperCase();
|
|
} else {
|
|
return value;
|
|
}
|
|
};
|
|
|
|
getCellProperties = (
|
|
columnProperties: ColumnProperties,
|
|
rowIndex: number,
|
|
) => {
|
|
const cellProperties: CellLayoutProperties = {
|
|
horizontalAlignment: this.getPropertyValue(
|
|
columnProperties.horizontalAlignment,
|
|
rowIndex,
|
|
),
|
|
verticalAlignment: this.getPropertyValue(
|
|
columnProperties.verticalAlignment,
|
|
rowIndex,
|
|
),
|
|
cellBackground: this.getPropertyValue(
|
|
columnProperties.cellBackground,
|
|
rowIndex,
|
|
),
|
|
buttonStyle: this.getPropertyValue(
|
|
columnProperties.buttonStyle,
|
|
rowIndex,
|
|
),
|
|
buttonLabelColor: this.getPropertyValue(
|
|
columnProperties.buttonLabelColor,
|
|
rowIndex,
|
|
),
|
|
buttonLabel: this.getPropertyValue(
|
|
columnProperties.buttonLabel,
|
|
rowIndex,
|
|
true,
|
|
),
|
|
textSize: this.getPropertyValue(columnProperties.textSize, rowIndex),
|
|
textColor: this.getPropertyValue(columnProperties.textColor, rowIndex),
|
|
fontStyle: this.getPropertyValue(columnProperties.fontStyle, rowIndex), //Fix this
|
|
};
|
|
return cellProperties;
|
|
};
|
|
|
|
getTableColumns = () => {
|
|
let columns: ReactTableColumnProps[] = [];
|
|
const hiddenColumns: ReactTableColumnProps[] = [];
|
|
const {
|
|
sortedColumn,
|
|
columnOrder,
|
|
columnSizeMap,
|
|
primaryColumns = {},
|
|
} = this.props;
|
|
let allColumns = Object.assign(
|
|
{},
|
|
this.createTablePrimaryColumns() || primaryColumns,
|
|
);
|
|
|
|
const sortColumn = sortedColumn?.column;
|
|
const sortOrder = sortedColumn?.asc;
|
|
if (columnOrder) {
|
|
allColumns = reorderColumns(allColumns, columnOrder);
|
|
}
|
|
const { componentWidth } = this.getComponentDimensions();
|
|
let totalColumnSizes = 0;
|
|
const defaultColumnWidth = 150;
|
|
for (const i in columnSizeMap) {
|
|
totalColumnSizes += columnSizeMap[i];
|
|
}
|
|
|
|
const allColumnProperties = Object.values(allColumns);
|
|
for (let index = 0; index < allColumnProperties.length; index++) {
|
|
const columnProperties = allColumnProperties[index];
|
|
const isHidden = !columnProperties.isVisible;
|
|
const accessor = columnProperties.id;
|
|
const columnData = {
|
|
Header: columnProperties.label,
|
|
accessor: accessor,
|
|
width:
|
|
columnSizeMap && columnSizeMap[accessor]
|
|
? columnSizeMap[accessor]
|
|
: defaultColumnWidth,
|
|
minWidth: 60,
|
|
draggable: true,
|
|
isHidden: false,
|
|
isAscOrder: columnProperties.id === sortColumn ? sortOrder : undefined,
|
|
isDerived: columnProperties.isDerived,
|
|
metaProperties: {
|
|
isHidden: isHidden,
|
|
type: columnProperties.columnType,
|
|
format: columnProperties?.outputFormat || "",
|
|
inputFormat: columnProperties?.inputFormat || "",
|
|
},
|
|
columnProperties: columnProperties,
|
|
Cell: (props: any) => {
|
|
const rowIndex: number = props.cell.row.index;
|
|
const cellProperties = this.getCellProperties(
|
|
columnProperties,
|
|
rowIndex,
|
|
);
|
|
if (columnProperties.columnType === "button") {
|
|
const buttonProps = {
|
|
isSelected: !!props.row.isSelected,
|
|
onCommandClick: (action: string, onComplete: () => void) =>
|
|
this.onCommandClick(rowIndex, action, onComplete),
|
|
backgroundColor: cellProperties.buttonStyle || "#29CCA3",
|
|
buttonLabelColor: cellProperties.buttonLabelColor || "#FFFFFF",
|
|
columnActions: [
|
|
{
|
|
id: columnProperties.id,
|
|
label: cellProperties.buttonLabel || "Action",
|
|
dynamicTrigger: columnProperties.onClick || "",
|
|
},
|
|
],
|
|
};
|
|
return renderActions(buttonProps, isHidden, cellProperties);
|
|
} else if (columnProperties.columnType === "dropdown") {
|
|
let options = [];
|
|
try {
|
|
options = JSON.parse(columnProperties.dropdownOptions || "");
|
|
} catch (e) {}
|
|
return renderDropdown({
|
|
options: options,
|
|
onItemSelect: this.onItemSelect,
|
|
onOptionChange: columnProperties.onOptionChange || "",
|
|
selectedIndex: isNumber(props.cell.value)
|
|
? props.cell.value
|
|
: undefined,
|
|
});
|
|
} else {
|
|
return renderCell(
|
|
props.cell.value,
|
|
columnProperties.columnType,
|
|
isHidden,
|
|
cellProperties,
|
|
componentWidth,
|
|
);
|
|
}
|
|
},
|
|
};
|
|
if (isHidden) {
|
|
columnData.isHidden = true;
|
|
hiddenColumns.push(columnData);
|
|
} else {
|
|
columns.push(columnData);
|
|
}
|
|
}
|
|
if (totalColumnSizes < componentWidth) {
|
|
const lastColumnIndex = columns.length - 1;
|
|
let remainingColumnsSize = 0;
|
|
for (let i = 0; i < columns.length - 1; i++) {
|
|
remainingColumnsSize += columns[i].width || defaultColumnWidth;
|
|
}
|
|
if (columns[lastColumnIndex]) {
|
|
columns[lastColumnIndex].width =
|
|
componentWidth - remainingColumnsSize < defaultColumnWidth
|
|
? defaultColumnWidth
|
|
: componentWidth - remainingColumnsSize; //Min remaining width to be defaultColumnWidth
|
|
}
|
|
}
|
|
if (hiddenColumns.length && this.props.renderMode === RenderModes.CANVAS) {
|
|
columns = columns.concat(hiddenColumns);
|
|
}
|
|
return columns.filter((column: ReactTableColumnProps) => column.accessor);
|
|
};
|
|
|
|
transformData = (
|
|
tableData: Array<Record<string, unknown>>,
|
|
columns: ReactTableColumnProps[],
|
|
) => {
|
|
const updatedTableData = [];
|
|
// For each row in the tableData (filteredTableData)
|
|
for (let row = 0; row < tableData.length; row++) {
|
|
// Get the row object
|
|
const data: { [key: string]: any } = tableData[row];
|
|
if (data) {
|
|
const tableRow: { [key: string]: any } = {};
|
|
// For each column in the expected columns of the table
|
|
for (let colIndex = 0; colIndex < columns.length; colIndex++) {
|
|
// Get the column properties
|
|
const column = columns[colIndex];
|
|
const { accessor } = column;
|
|
let value = data[accessor];
|
|
if (column.metaProperties) {
|
|
const type = column.metaProperties.type;
|
|
switch (type) {
|
|
case ColumnTypes.DATE:
|
|
let isValidDate = true;
|
|
let outputFormat = column.metaProperties.format;
|
|
let inputFormat;
|
|
try {
|
|
const type = column.metaProperties.inputFormat;
|
|
if (type !== "Epoch" && type !== "Milliseconds") {
|
|
inputFormat = type;
|
|
moment(value, inputFormat);
|
|
} else if (!isNumber(value)) {
|
|
isValidDate = false;
|
|
}
|
|
} catch (e) {
|
|
isValidDate = false;
|
|
}
|
|
if (isValidDate) {
|
|
if (outputFormat === "SAME_AS_INPUT") {
|
|
outputFormat = inputFormat;
|
|
}
|
|
if (column.metaProperties.inputFormat === "Milliseconds") {
|
|
value = Number(value);
|
|
} else if (column.metaProperties.inputFormat === "Epoch") {
|
|
value = 1000 * Number(value);
|
|
}
|
|
tableRow[accessor] = moment(value, inputFormat).format(
|
|
outputFormat,
|
|
);
|
|
} else if (value) {
|
|
tableRow[accessor] = "Invalid Value";
|
|
} else {
|
|
tableRow[accessor] = "";
|
|
}
|
|
break;
|
|
default:
|
|
const data =
|
|
isString(value) || isNumber(value)
|
|
? value
|
|
: isUndefined(value)
|
|
? ""
|
|
: JSON.stringify(value);
|
|
tableRow[accessor] = data;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
updatedTableData.push(tableRow);
|
|
}
|
|
}
|
|
|
|
return updatedTableData;
|
|
};
|
|
|
|
getParsedComputedValues = (value: string | Array<unknown>) => {
|
|
let computedValues: Array<unknown> = [];
|
|
if (isString(value)) {
|
|
try {
|
|
computedValues = JSON.parse(value);
|
|
} catch (e) {
|
|
log.debug("Error parsing column value: ", value);
|
|
}
|
|
} else if (Array.isArray(value)) {
|
|
computedValues = value;
|
|
} else {
|
|
log.debug("Error parsing column values:", value);
|
|
}
|
|
return computedValues;
|
|
};
|
|
|
|
filterTableData = () => {
|
|
const {
|
|
searchText,
|
|
sortedColumn,
|
|
filters,
|
|
tableData,
|
|
derivedColumns,
|
|
} = this.props;
|
|
if (!tableData || !tableData.length) {
|
|
return [];
|
|
}
|
|
const derivedTableData: Array<Record<string, unknown>> = [...tableData];
|
|
// If we've already computed the columns list
|
|
if (this.props.primaryColumns) {
|
|
const primaryColumns = this.props.primaryColumns;
|
|
const columnIds = Object.keys(this.props.primaryColumns);
|
|
// For each column in the table
|
|
columnIds.forEach((columnId: string) => {
|
|
// Get the column properties
|
|
const column: ColumnProperties = primaryColumns[columnId];
|
|
let computedValues: Array<unknown> = [];
|
|
|
|
if (column && column.computedValue) {
|
|
computedValues = this.getParsedComputedValues(column.computedValue);
|
|
}
|
|
|
|
if (computedValues.length === 0) {
|
|
if (derivedColumns) {
|
|
// Find the derived column with the same column id as the current column
|
|
const derivedColumn = derivedColumns[columnId];
|
|
// if such a derived column exists, use it.
|
|
if (derivedColumn) {
|
|
computedValues = this.getParsedComputedValues(
|
|
derivedColumn.computedValue,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fill the values from the computed values into the table data.
|
|
for (let index = 0; index < computedValues.length; index++) {
|
|
derivedTableData[index] = {
|
|
...derivedTableData[index],
|
|
[columnId]: computedValues[index],
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
let sortedTableData: any[];
|
|
const columns = this.getTableColumns() || [];
|
|
const searchKey = searchText ? searchText.toUpperCase() : "";
|
|
if (sortedColumn) {
|
|
const sortColumn = sortedColumn.column;
|
|
const sortOrder = sortedColumn.asc;
|
|
sortedTableData = sortTableFunction(
|
|
derivedTableData,
|
|
columns,
|
|
sortColumn,
|
|
sortOrder,
|
|
);
|
|
} else {
|
|
sortedTableData = [...derivedTableData];
|
|
}
|
|
const finalTableData = sortedTableData.filter(
|
|
(item: { [key: string]: any }) => {
|
|
const searchFound = searchKey
|
|
? Object.values(item)
|
|
.join(", ")
|
|
.toUpperCase()
|
|
.includes(searchKey)
|
|
: true;
|
|
if (!searchFound) return false;
|
|
if (!filters || filters.length === 0) return true;
|
|
const filterOperator: Operator =
|
|
filters.length >= 2 ? filters[1].operator : OperatorTypes.OR;
|
|
let filter = filterOperator === OperatorTypes.AND;
|
|
for (let i = 0; i < filters.length; i++) {
|
|
const filterValue = compare(
|
|
item[filters[i].column],
|
|
filters[i].value,
|
|
filters[i].condition,
|
|
);
|
|
if (filterOperator === OperatorTypes.AND) {
|
|
filter = filter && filterValue;
|
|
} else {
|
|
filter = filter || filterValue;
|
|
}
|
|
}
|
|
return filter;
|
|
},
|
|
);
|
|
return finalTableData;
|
|
};
|
|
|
|
getEmptyRow = () => {
|
|
const columnKeys: string[] = getAllTableColumnKeys(this.props.tableData);
|
|
const selectedRow: { [key: string]: any } = {};
|
|
for (let i = 0; i < columnKeys.length; i++) {
|
|
selectedRow[columnKeys[i]] = "";
|
|
}
|
|
return selectedRow;
|
|
};
|
|
|
|
getSelectedRow = (
|
|
filteredTableData: Array<Record<string, unknown>>,
|
|
selectedRowIndex?: number,
|
|
) => {
|
|
if (
|
|
selectedRowIndex === undefined ||
|
|
selectedRowIndex === -1 ||
|
|
selectedRowIndex === null
|
|
) {
|
|
return this.getEmptyRow();
|
|
}
|
|
return {
|
|
...filteredTableData[selectedRowIndex],
|
|
};
|
|
};
|
|
|
|
getDerivedColumns = (
|
|
derivedColumns: Record<string, ColumnProperties>,
|
|
tableColumnCount: number,
|
|
) => {
|
|
if (!derivedColumns) return [];
|
|
//update index property of all columns in new derived columns
|
|
return (
|
|
Object.keys(derivedColumns)?.map((columnId: string, index: number) => {
|
|
return {
|
|
...derivedColumns[columnId],
|
|
index: index + tableColumnCount,
|
|
};
|
|
}) || []
|
|
);
|
|
};
|
|
|
|
createTablePrimaryColumns = ():
|
|
| Record<string, ColumnProperties>
|
|
| undefined => {
|
|
const {
|
|
tableData,
|
|
primaryColumns = {},
|
|
columnNameMap = {},
|
|
columnTypeMap = {},
|
|
derivedColumns = {},
|
|
migrated,
|
|
} = this.props;
|
|
|
|
const previousColumnIds = Object.keys(primaryColumns);
|
|
const tableColumns: Record<string, ColumnProperties> = {};
|
|
//Get table level styles
|
|
const tableStyles = getTableStyles(this.props);
|
|
const columnKeys: string[] = getAllTableColumnKeys(tableData);
|
|
// Generate default column properties for all columns
|
|
// But donot replace existing columns with the same id
|
|
for (let index = 0; index < columnKeys.length; index++) {
|
|
const i = columnKeys[index];
|
|
const prevIndex = previousColumnIds.indexOf(i);
|
|
if (prevIndex > -1) {
|
|
// we found an existing property with the same column id use the previous properties
|
|
tableColumns[i] = primaryColumns[i];
|
|
} else {
|
|
const columnProperties = getDefaultColumnProperties(
|
|
i,
|
|
index,
|
|
this.props.widgetName,
|
|
);
|
|
if (migrated === false) {
|
|
if ((columnNameMap as Record<string, string>)[i]) {
|
|
columnProperties.label = columnNameMap[i];
|
|
}
|
|
if (
|
|
(columnTypeMap as Record<
|
|
string,
|
|
{ type: ColumnTypes; inputFormat?: string; format?: string }
|
|
>)[i]
|
|
) {
|
|
columnProperties.columnType = columnTypeMap[i].type;
|
|
columnProperties.inputFormat = columnTypeMap[i].inputFormat;
|
|
columnProperties.outputFormat = columnTypeMap[i].format;
|
|
}
|
|
}
|
|
//add column properties along with table level styles
|
|
tableColumns[columnProperties.id] = {
|
|
...columnProperties,
|
|
...tableStyles,
|
|
};
|
|
}
|
|
}
|
|
// Get derived columns
|
|
const updatedDerivedColumns = this.getDerivedColumns(
|
|
derivedColumns,
|
|
Object.keys(tableColumns).length,
|
|
);
|
|
|
|
//add derived columns to primary columns
|
|
updatedDerivedColumns.forEach((derivedColumn: ColumnProperties) => {
|
|
tableColumns[derivedColumn.id] = derivedColumn;
|
|
});
|
|
|
|
const newColumnIds = Object.keys(tableColumns);
|
|
if (xor(previousColumnIds, newColumnIds).length > 0) return tableColumns;
|
|
else return;
|
|
};
|
|
|
|
updateColumnProperties = (
|
|
tableColumns?: Record<string, ColumnProperties>,
|
|
) => {
|
|
const { primaryColumns = {} } = this.props;
|
|
const { columnOrder, migrated } = this.props;
|
|
if (tableColumns) {
|
|
const previousColumnIds = Object.keys(primaryColumns);
|
|
const newColumnIds = Object.keys(tableColumns);
|
|
|
|
if (xor(previousColumnIds, newColumnIds).length > 0) {
|
|
const columnIdsToAdd = without(newColumnIds, ...previousColumnIds);
|
|
|
|
const propertiesToAdd: Record<string, unknown> = {};
|
|
columnIdsToAdd.forEach((id: string) => {
|
|
Object.entries(tableColumns[id]).forEach(([key, value]) => {
|
|
propertiesToAdd[`primaryColumns.${id}.${key}`] = value;
|
|
});
|
|
});
|
|
|
|
// If new columnOrders have different values from the original columnOrders
|
|
if (xor(newColumnIds, columnOrder).length > 0) {
|
|
propertiesToAdd["columnOrder"] = newColumnIds;
|
|
}
|
|
|
|
const pathsToDelete: string[] = [];
|
|
if (migrated === false) {
|
|
propertiesToAdd["migrated"] = true;
|
|
}
|
|
|
|
super.batchUpdateWidgetProperty(propertiesToAdd);
|
|
if (previousColumnIds.length > newColumnIds.length) {
|
|
const columnsIdsToDelete = without(
|
|
previousColumnIds,
|
|
...newColumnIds,
|
|
);
|
|
columnsIdsToDelete.forEach((id: string) => {
|
|
pathsToDelete.push(`primaryColumns.${id}`);
|
|
});
|
|
// We need to wait for the above updates to finish
|
|
// Todo(abhinav): This is not correct. The platform should accept multiple types of updates
|
|
// That approach should be performant.
|
|
setTimeout(() => {
|
|
super.deleteWidgetProperty(pathsToDelete);
|
|
}, 1000);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
componentDidMount() {
|
|
const { tableData } = this.props;
|
|
let newPrimaryColumns;
|
|
// When we have tableData, the primaryColumns order is unlikely to change
|
|
// When we don't have tableData primaryColumns will not be available, so let's let it be.
|
|
|
|
if (tableData.length > 0) {
|
|
newPrimaryColumns = this.createTablePrimaryColumns();
|
|
}
|
|
if (!newPrimaryColumns) {
|
|
const filteredTableData = this.filterTableData();
|
|
this.props.updateWidgetMetaProperty(
|
|
"filteredTableData",
|
|
filteredTableData,
|
|
);
|
|
} else {
|
|
this.updateColumnProperties(newPrimaryColumns);
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps: TableWidgetProps) {
|
|
const { primaryColumns = {} } = this.props;
|
|
|
|
// Check if data is modifed by comparing the stringified versions of the previous and next tableData
|
|
const tableDataModified =
|
|
JSON.stringify(this.props.tableData) !==
|
|
JSON.stringify(prevProps.tableData);
|
|
|
|
// let hasPrimaryColumnsComputedValueChanged = false;
|
|
// const oldComputedValues = Object.values(
|
|
// prevProps.primaryColumns || {},
|
|
// )?.map((column: ColumnProperties) => column.computedValue);
|
|
// const newComputedValues = Object.values(
|
|
// this.props.primaryColumns || {},
|
|
// )?.map((column: ColumnProperties) => column.computedValue);
|
|
// if (!isEqual(oldComputedValues, newComputedValues)) {
|
|
// hasPrimaryColumnsComputedValueChanged = true;
|
|
// }
|
|
|
|
let hasPrimaryColumnsChanged = false;
|
|
// If the user has changed the tableData OR
|
|
// The binding has returned a new value
|
|
if (tableDataModified) {
|
|
// Get columns keys from this.props.tableData
|
|
const columnIds: string[] = getAllTableColumnKeys(this.props.tableData);
|
|
// Get column keys from columns except for derivedColumns
|
|
const primaryColumnIds = Object.keys(primaryColumns).filter(
|
|
(id: string) => {
|
|
return !primaryColumns[id].isDerived; // Filter out the derived columns
|
|
},
|
|
);
|
|
// If the keys which exist in the tableData are different from the ones available in primaryColumns
|
|
if (xor(columnIds, primaryColumnIds).length > 0) {
|
|
const newTableColumns = this.createTablePrimaryColumns(); // This updates the widget
|
|
hasPrimaryColumnsChanged = !!newTableColumns;
|
|
this.updateColumnProperties(newTableColumns);
|
|
}
|
|
}
|
|
|
|
// If tableData has changed or
|
|
// Table filters have changed or
|
|
// Table search Text has changed or
|
|
// Sorting has changed
|
|
// filteredTableData is not created
|
|
if (
|
|
!hasPrimaryColumnsChanged &&
|
|
(JSON.stringify(this.props.filters) !==
|
|
JSON.stringify(prevProps.filters) ||
|
|
this.props.searchText !== prevProps.searchText ||
|
|
JSON.stringify(this.props.sortedColumn) !==
|
|
JSON.stringify(prevProps.sortedColumn) ||
|
|
JSON.stringify(this.props.primaryColumns) !==
|
|
JSON.stringify(prevProps.primaryColumns) ||
|
|
this.props.filteredTableData === undefined)
|
|
) {
|
|
if (this.props.primaryColumns && Object.keys(primaryColumns).length > 0) {
|
|
const filteredTableData = this.filterTableData();
|
|
|
|
if (
|
|
JSON.stringify(filteredTableData) !==
|
|
JSON.stringify(this.props.filteredTableData)
|
|
) {
|
|
// Update filteredTableData meta property
|
|
this.props.updateWidgetMetaProperty(
|
|
"filteredTableData",
|
|
filteredTableData,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the user has switched the mutiple row selection feature
|
|
if (this.props.multiRowSelection !== prevProps.multiRowSelection) {
|
|
// It is switched ON:
|
|
if (this.props.multiRowSelection) {
|
|
// Use the selectedRowIndex if available as default selected index
|
|
const selectedRowIndices = this.props.selectedRowIndex
|
|
? [this.props.selectedRowIndex]
|
|
: []; // Else use an empty array
|
|
this.props.updateWidgetMetaProperty(
|
|
"selectedRowIndices",
|
|
selectedRowIndices,
|
|
);
|
|
this.props.updateWidgetMetaProperty("selectedRowIndex", -1);
|
|
} else {
|
|
this.props.updateWidgetMetaProperty("selectedRowIndices", []);
|
|
}
|
|
}
|
|
|
|
// If the user changed the defaultSelectedRow(s)
|
|
if (!isEqual(this.props.defaultSelectedRow, prevProps.defaultSelectedRow)) {
|
|
//Runs only when defaultSelectedRow is changed from property pane
|
|
if (!this.props.multiRowSelection) {
|
|
const selectedRowIndex = isNumber(this.props.defaultSelectedRow)
|
|
? this.props.defaultSelectedRow
|
|
: -1;
|
|
this.props.updateWidgetMetaProperty(
|
|
"selectedRowIndex",
|
|
selectedRowIndex,
|
|
);
|
|
} else {
|
|
const selectedRowIndices = Array.isArray(this.props.defaultSelectedRow)
|
|
? this.props.defaultSelectedRow
|
|
: [];
|
|
this.props.updateWidgetMetaProperty(
|
|
"selectedRowIndices",
|
|
selectedRowIndices,
|
|
);
|
|
}
|
|
}
|
|
|
|
if (this.props.pageSize !== prevProps.pageSize) {
|
|
super.executeAction({
|
|
dynamicString: this.props.onPageSizeChange,
|
|
event: {
|
|
type: EventType.ON_PAGE_SIZE_CHANGE,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
getSelectedRowIndexes = (selectedRowIndices: string) => {
|
|
return selectedRowIndices
|
|
? selectedRowIndices.split(",").map((i) => Number(i))
|
|
: [];
|
|
};
|
|
|
|
getPageView() {
|
|
const { hiddenColumns, pageSize } = this.props;
|
|
const filteredTableData = this.filterTableData();
|
|
|
|
const computedSelectedRowIndices = Array.isArray(
|
|
this.props.selectedRowIndices,
|
|
)
|
|
? this.props.selectedRowIndices
|
|
: [];
|
|
const tableColumns = this.getTableColumns() || [];
|
|
|
|
const transformedData = this.transformData(
|
|
filteredTableData || [],
|
|
tableColumns,
|
|
);
|
|
|
|
const serverSidePaginationEnabled = (this.props
|
|
.serverSidePaginationEnabled &&
|
|
this.props.serverSidePaginationEnabled) as boolean;
|
|
let pageNo = this.props.pageNo;
|
|
|
|
if (pageNo === undefined) {
|
|
pageNo = 1;
|
|
this.props.updateWidgetMetaProperty("pageNo", pageNo);
|
|
}
|
|
const { componentWidth, componentHeight } = this.getComponentDimensions();
|
|
|
|
return (
|
|
<Suspense fallback={<Skeleton />}>
|
|
<ReactTableComponent
|
|
height={componentHeight}
|
|
width={componentWidth}
|
|
tableData={transformedData}
|
|
columns={tableColumns}
|
|
isLoading={this.props.isLoading}
|
|
widgetId={this.props.widgetId}
|
|
widgetName={this.props.widgetName}
|
|
searchKey={this.props.searchText}
|
|
editMode={this.props.renderMode === RenderModes.CANVAS}
|
|
hiddenColumns={hiddenColumns}
|
|
columnOrder={this.props.columnOrder}
|
|
triggerRowSelection={this.props.triggerRowSelection}
|
|
columnSizeMap={this.props.columnSizeMap}
|
|
pageSize={Math.max(1, pageSize)}
|
|
onCommandClick={this.onCommandClick}
|
|
selectedRowIndex={
|
|
this.props.selectedRowIndex === undefined
|
|
? -1
|
|
: this.props.selectedRowIndex
|
|
}
|
|
multiRowSelection={this.props.multiRowSelection}
|
|
selectedRowIndices={computedSelectedRowIndices}
|
|
serverSidePaginationEnabled={serverSidePaginationEnabled}
|
|
onRowClick={this.handleRowClick}
|
|
pageNo={pageNo}
|
|
nextPageClick={this.handleNextPageClick}
|
|
prevPageClick={this.handlePrevPageClick}
|
|
handleResizeColumn={this.handleResizeColumn}
|
|
updatePageNo={this.updatePageNumber}
|
|
updateHiddenColumns={(hiddenColumns?: string[]) => {
|
|
super.updateWidgetProperty("hiddenColumns", hiddenColumns);
|
|
}}
|
|
handleReorderColumn={this.handleReorderColumn}
|
|
disableDrag={(disable: boolean) => {
|
|
this.disableDrag(disable);
|
|
}}
|
|
searchTableData={this.handleSearchTable}
|
|
filters={this.props.filters}
|
|
applyFilter={(filters: ReactTableFilter[]) => {
|
|
this.resetSelectedRowIndex();
|
|
this.props.updateWidgetMetaProperty("filters", filters);
|
|
}}
|
|
compactMode={this.props.compactMode || CompactModeTypes.DEFAULT}
|
|
updateCompactMode={(compactMode: CompactMode) => {
|
|
this.props.updateWidgetMetaProperty("compactMode", compactMode);
|
|
}}
|
|
sortTableColumn={this.handleColumnSorting}
|
|
/>
|
|
</Suspense>
|
|
);
|
|
}
|
|
|
|
handleReorderColumn = (columnOrder: string[]) => {
|
|
if (this.props.renderMode === RenderModes.CANVAS) {
|
|
super.updateWidgetProperty("columnOrder", columnOrder);
|
|
} else this.props.updateWidgetMetaProperty("columnOrder", columnOrder);
|
|
};
|
|
|
|
handleColumnSorting = (column: string, asc: boolean) => {
|
|
this.resetSelectedRowIndex();
|
|
if (column === "") {
|
|
this.props.updateWidgetMetaProperty("sortedColumn", undefined);
|
|
} else {
|
|
this.props.updateWidgetMetaProperty("sortedColumn", {
|
|
column: column,
|
|
asc: asc,
|
|
});
|
|
}
|
|
};
|
|
|
|
handleResizeColumn = (columnSizeMap: { [key: string]: number }) => {
|
|
if (this.props.renderMode === RenderModes.CANVAS) {
|
|
super.updateWidgetProperty("columnSizeMap", columnSizeMap);
|
|
} else {
|
|
this.props.updateWidgetMetaProperty("columnSizeMap", columnSizeMap);
|
|
}
|
|
};
|
|
|
|
handleSearchTable = (searchKey: any) => {
|
|
const { onSearchTextChanged } = this.props;
|
|
this.resetSelectedRowIndex();
|
|
this.props.updateWidgetMetaProperty("pageNo", 1);
|
|
this.props.updateWidgetMetaProperty("searchText", searchKey, {
|
|
dynamicString: onSearchTextChanged,
|
|
event: {
|
|
type: EventType.ON_SEARCH,
|
|
},
|
|
});
|
|
};
|
|
|
|
updateHiddenColumns = (hiddenColumns?: string[]) => {
|
|
super.updateWidgetProperty("hiddenColumns", hiddenColumns);
|
|
};
|
|
|
|
onCommandClick = (
|
|
rowIndex: number,
|
|
action: string,
|
|
onComplete: () => void,
|
|
) => {
|
|
try {
|
|
const rowData = [this.props.filteredTableData[rowIndex]];
|
|
const { jsSnippets } = getDynamicBindings(action);
|
|
const modifiedAction = jsSnippets.reduce((prev: string, next: string) => {
|
|
return prev + `{{(currentRow) => { ${next} }}} `;
|
|
}, "");
|
|
|
|
super.executeAction({
|
|
dynamicString: modifiedAction,
|
|
event: {
|
|
type: EventType.ON_CLICK,
|
|
callback: onComplete,
|
|
},
|
|
responseData: rowData,
|
|
});
|
|
} catch (error) {
|
|
log.debug("Error parsing row action", error);
|
|
}
|
|
};
|
|
|
|
onItemSelect = (action: string) => {
|
|
super.executeAction({
|
|
dynamicString: action,
|
|
event: {
|
|
type: EventType.ON_OPTION_CHANGE,
|
|
},
|
|
});
|
|
};
|
|
|
|
handleRowClick = (rowData: Record<string, unknown>, index: number) => {
|
|
if (this.props.multiRowSelection) {
|
|
const selectedRowIndices = this.props.selectedRowIndices
|
|
? [...this.props.selectedRowIndices]
|
|
: [];
|
|
if (selectedRowIndices.includes(index)) {
|
|
const rowIndex = selectedRowIndices.indexOf(index);
|
|
selectedRowIndices.splice(rowIndex, 1);
|
|
} else {
|
|
selectedRowIndices.push(index);
|
|
}
|
|
this.props.updateWidgetMetaProperty(
|
|
"selectedRowIndices",
|
|
selectedRowIndices,
|
|
);
|
|
this.props.updateWidgetMetaProperty(
|
|
"selectedRows",
|
|
this.props.filteredTableData.filter(
|
|
(item: Record<string, unknown>, i: number) => {
|
|
return selectedRowIndices.includes(i);
|
|
},
|
|
),
|
|
);
|
|
} else {
|
|
const selectedRowIndex = isNumber(this.props.selectedRowIndex)
|
|
? this.props.selectedRowIndex
|
|
: -1;
|
|
if (selectedRowIndex === index) {
|
|
index = -1;
|
|
} else {
|
|
this.props.updateWidgetMetaProperty(
|
|
"selectedRow",
|
|
this.props.filteredTableData[index],
|
|
{
|
|
dynamicString: this.props.onRowSelected,
|
|
event: {
|
|
type: EventType.ON_ROW_SELECTED,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
this.props.updateWidgetMetaProperty("selectedRowIndex", index);
|
|
}
|
|
};
|
|
|
|
updatePageNumber = (pageNo: number, event?: EventType) => {
|
|
if (event) {
|
|
this.props.updateWidgetMetaProperty("pageNo", pageNo, {
|
|
dynamicString: this.props.onPageChange,
|
|
event: {
|
|
type: event,
|
|
},
|
|
});
|
|
} else {
|
|
this.props.updateWidgetMetaProperty("pageNo", pageNo);
|
|
}
|
|
if (this.props.onPageChange) {
|
|
this.resetSelectedRowIndex();
|
|
}
|
|
};
|
|
|
|
handleNextPageClick = () => {
|
|
let pageNo = this.props.pageNo || 1;
|
|
pageNo = pageNo + 1;
|
|
this.props.updateWidgetMetaProperty("pageNo", pageNo, {
|
|
dynamicString: this.props.onPageChange,
|
|
event: {
|
|
type: EventType.ON_NEXT_PAGE,
|
|
},
|
|
});
|
|
if (this.props.onPageChange) {
|
|
this.resetSelectedRowIndex();
|
|
}
|
|
};
|
|
|
|
resetSelectedRowIndex = () => {
|
|
if (!this.props.multiRowSelection) {
|
|
const selectedRowIndex = isNumber(this.props.defaultSelectedRow)
|
|
? this.props.defaultSelectedRow
|
|
: -1;
|
|
this.props.updateWidgetMetaProperty("selectedRowIndex", selectedRowIndex);
|
|
} else {
|
|
const selectedRowIndices = Array.isArray(this.props.defaultSelectedRow)
|
|
? this.props.defaultSelectedRow
|
|
: [];
|
|
this.props.updateWidgetMetaProperty(
|
|
"selectedRowIndices",
|
|
selectedRowIndices,
|
|
);
|
|
}
|
|
};
|
|
|
|
handlePrevPageClick = () => {
|
|
let pageNo = this.props.pageNo || 1;
|
|
pageNo = pageNo - 1;
|
|
if (pageNo >= 1) {
|
|
this.props.updateWidgetMetaProperty("pageNo", pageNo, {
|
|
dynamicString: this.props.onPageChange,
|
|
event: {
|
|
type: EventType.ON_PREV_PAGE,
|
|
},
|
|
});
|
|
if (this.props.onPageChange) {
|
|
this.resetSelectedRowIndex();
|
|
}
|
|
}
|
|
};
|
|
|
|
getWidgetType(): WidgetType {
|
|
return "TABLE_WIDGET";
|
|
}
|
|
}
|
|
|
|
export default TableWidget;
|
|
export const ProfiledTableWidget = Sentry.withProfiler(withMeta(TableWidget));
|