## Description Adds a new currency column type on the table widget. #### PR fixes following issue(s) Fixes #5632 > if no issue exists, please create an issue and ask the maintainers about this first > > #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change - New feature (non-breaking change which adds functionality) > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [ ] JUnit - [ ] Jest - [x] Cypress > > #### Test Plan > https://github.com/appsmithorg/TestSmith/issues/2447 > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
2900 lines
92 KiB
TypeScript
2900 lines
92 KiB
TypeScript
import React, { lazy, Suspense } from "react";
|
|
import log from "loglevel";
|
|
import memoizeOne from "memoize-one";
|
|
|
|
import _, {
|
|
filter,
|
|
isArray,
|
|
isEmpty,
|
|
isNil,
|
|
isNumber,
|
|
isObject,
|
|
isString,
|
|
merge,
|
|
orderBy,
|
|
pickBy,
|
|
union,
|
|
without,
|
|
xor,
|
|
xorWith,
|
|
} from "lodash";
|
|
|
|
import type { WidgetProps, WidgetState } from "widgets/BaseWidget";
|
|
import BaseWidget from "widgets/BaseWidget";
|
|
import {
|
|
RenderModes,
|
|
WIDGET_PADDING,
|
|
WIDGET_TAGS,
|
|
} from "constants/WidgetConstants";
|
|
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
|
import Skeleton from "components/utils/Skeleton";
|
|
import { noop, retryPromise } from "utils/AppsmithUtils";
|
|
import type {
|
|
ColumnProperties,
|
|
ReactTableColumnProps,
|
|
ReactTableFilter,
|
|
} from "../component/Constants";
|
|
import {
|
|
AddNewRowActions,
|
|
CompactModeTypes,
|
|
DEFAULT_FILTER,
|
|
SORT_ORDER,
|
|
SortOrderTypes,
|
|
StickyType,
|
|
} from "../component/Constants";
|
|
import type {
|
|
EditableCell,
|
|
OnColumnEventArgs,
|
|
TableWidgetProps,
|
|
TransientDataPayload,
|
|
} from "../constants";
|
|
import {
|
|
ActionColumnTypes,
|
|
ALLOW_TABLE_WIDGET_SERVER_SIDE_FILTERING,
|
|
ColumnTypes,
|
|
DEFAULT_BUTTON_LABEL,
|
|
DEFAULT_COLUMN_WIDTH,
|
|
DEFAULT_MENU_BUTTON_LABEL,
|
|
DEFAULT_MENU_VARIANT,
|
|
defaultEditableCell,
|
|
EditableCellActions,
|
|
InlineEditingSaveOptions,
|
|
ORIGINAL_INDEX_KEY,
|
|
PaginationDirection,
|
|
TABLE_COLUMN_ORDER_KEY,
|
|
} from "../constants";
|
|
import derivedProperties from "./parseDerivedProperties";
|
|
import {
|
|
createEditActionColumn,
|
|
deleteLocalTableColumnOrderByWidgetId,
|
|
generateLocalNewColumnOrderFromStickyValue,
|
|
generateNewColumnOrderFromStickyValue,
|
|
getAllStickyColumnsCount,
|
|
getAllTableColumnKeys,
|
|
getBooleanPropertyValue,
|
|
getCellProperties,
|
|
getColumnOrderByWidgetIdFromLS,
|
|
getColumnType,
|
|
getDefaultColumnProperties,
|
|
getDerivedColumns,
|
|
getSelectRowIndex,
|
|
getSelectRowIndices,
|
|
getTableStyles,
|
|
isColumnTypeEditable,
|
|
updateAndSyncTableLocalColumnOrders,
|
|
} from "./utilities";
|
|
import contentConfig from "./propertyConfig/contentConfig";
|
|
import styleConfig from "./propertyConfig/styleConfig";
|
|
import type { BatchPropertyUpdatePayload } from "actions/controlActions";
|
|
import type { IconName } from "@blueprintjs/icons";
|
|
import { IconNames } from "@blueprintjs/icons";
|
|
import { Colors } from "constants/Colors";
|
|
import equal from "fast-deep-equal/es6";
|
|
import {
|
|
DefaultAutocompleteDefinitions,
|
|
sanitizeKey,
|
|
} from "widgets/WidgetUtils";
|
|
import PlainTextCell from "../component/cellComponents/PlainTextCell";
|
|
import { ButtonCell } from "../component/cellComponents/ButtonCell";
|
|
import { MenuButtonCell } from "../component/cellComponents/MenuButtonCell";
|
|
import { ImageCell } from "../component/cellComponents/ImageCell";
|
|
import { VideoCell } from "../component/cellComponents/VideoCell";
|
|
import { IconButtonCell } from "../component/cellComponents/IconButtonCell";
|
|
import { EditActionCell } from "../component/cellComponents/EditActionsCell";
|
|
import { klona as clone } from "klona";
|
|
import { CheckboxCell } from "../component/cellComponents/CheckboxCell";
|
|
import { SwitchCell } from "../component/cellComponents/SwitchCell";
|
|
import { SelectCell } from "../component/cellComponents/SelectCell";
|
|
import { CellWrapper } from "../component/TableStyledWrappers";
|
|
import localStorage from "utils/localStorage";
|
|
import type { SetterConfig, Stylesheet } from "entities/AppTheming";
|
|
import { DateCell } from "../component/cellComponents/DateCell";
|
|
import type { MenuItem } from "widgets/MenuButtonWidget/constants";
|
|
import { MenuItemsSource } from "widgets/MenuButtonWidget/constants";
|
|
import { TimePrecision } from "widgets/DatePickerWidget2/constants";
|
|
import type { getColumns } from "./reactTableUtils/getColumnsPureFn";
|
|
import { getMemoiseGetColumnsWithLocalStorageFn } from "./reactTableUtils/getColumnsPureFn";
|
|
import type {
|
|
tableData,
|
|
transformDataWithEditableCell,
|
|
} from "./reactTableUtils/transformDataPureFn";
|
|
import { getMemoiseTransformDataWithEditableCell } from "./reactTableUtils/transformDataPureFn";
|
|
import type { ExtraDef } from "utils/autocomplete/defCreatorUtils";
|
|
import { generateTypeDef } from "utils/autocomplete/defCreatorUtils";
|
|
import type {
|
|
AnvilConfig,
|
|
AutocompletionDefinitions,
|
|
PropertyUpdates,
|
|
SnipingModeProperty,
|
|
} from "WidgetProvider/constants";
|
|
import type {
|
|
WidgetQueryConfig,
|
|
WidgetQueryGenerationFormConfig,
|
|
} from "WidgetQueryGenerators/types";
|
|
import type { DynamicPath } from "utils/DynamicBindingUtils";
|
|
import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants";
|
|
import {
|
|
FlexVerticalAlignment,
|
|
ResponsiveBehavior,
|
|
} from "layoutSystems/common/utils/constants";
|
|
import IconSVG from "../icon.svg";
|
|
|
|
const ReactTableComponent = lazy(async () =>
|
|
retryPromise(async () => import("../component")),
|
|
);
|
|
|
|
const emptyArr: any = [];
|
|
|
|
type addNewRowToTable = (
|
|
tableData: tableData,
|
|
isAddRowInProgress: boolean,
|
|
newRowContent: Record<string, unknown>,
|
|
) => tableData;
|
|
|
|
const getMemoisedAddNewRow = (): addNewRowToTable =>
|
|
memoizeOne((tableData, isAddRowInProgress, newRowContent) => {
|
|
if (isAddRowInProgress) {
|
|
return [newRowContent, ...tableData];
|
|
}
|
|
return tableData;
|
|
});
|
|
|
|
class TableWidgetV2 extends BaseWidget<TableWidgetProps, WidgetState> {
|
|
inlineEditTimer: number | null = null;
|
|
memoisedAddNewRow: addNewRowToTable;
|
|
memoiseGetColumnsWithLocalStorage: (localStorage: any) => getColumns;
|
|
memoiseTransformDataWithEditableCell: transformDataWithEditableCell;
|
|
|
|
static type = "TABLE_WIDGET_V2";
|
|
|
|
static preloadConfig = true;
|
|
|
|
static getConfig() {
|
|
return {
|
|
name: "Table",
|
|
iconSVG: IconSVG,
|
|
tags: [WIDGET_TAGS.SUGGESTED_WIDGETS, WIDGET_TAGS.DISPLAY],
|
|
needsMeta: true,
|
|
needsHeightForContent: true,
|
|
};
|
|
}
|
|
|
|
static getDefaults() {
|
|
return {
|
|
flexVerticalAlignment: FlexVerticalAlignment.Top,
|
|
responsiveBehavior: ResponsiveBehavior.Fill,
|
|
minWidth: FILL_WIDGET_MIN_WIDTH,
|
|
rows: 28,
|
|
canFreezeColumn: true,
|
|
columnUpdatedAt: Date.now(),
|
|
columns: 34,
|
|
animateLoading: true,
|
|
defaultSelectedRowIndex: 0,
|
|
defaultSelectedRowIndices: [0],
|
|
label: "Data",
|
|
widgetName: "Table",
|
|
searchKey: "",
|
|
textSize: "0.875rem",
|
|
horizontalAlignment: "LEFT",
|
|
verticalAlignment: "CENTER",
|
|
totalRecordsCount: 0,
|
|
defaultPageSize: 0,
|
|
dynamicPropertyPathList: [],
|
|
borderColor: Colors.GREY_5,
|
|
borderWidth: "1",
|
|
dynamicBindingPathList: [],
|
|
primaryColumns: {},
|
|
tableData: "",
|
|
columnWidthMap: {},
|
|
columnOrder: [],
|
|
enableClientSideSearch: true,
|
|
isVisibleSearch: true,
|
|
isVisibleFilters: true,
|
|
isVisibleDownload: true,
|
|
isVisiblePagination: true,
|
|
isSortable: true,
|
|
delimiter: ",",
|
|
version: 2,
|
|
inlineEditingSaveOption: InlineEditingSaveOptions.ROW_LEVEL,
|
|
enableServerSideFiltering: TableWidgetV2.getFeatureFlag(
|
|
ALLOW_TABLE_WIDGET_SERVER_SIDE_FILTERING,
|
|
)
|
|
? false
|
|
: undefined,
|
|
};
|
|
}
|
|
|
|
static getMethods() {
|
|
return {
|
|
getQueryGenerationConfig: (widget: WidgetProps) => {
|
|
return {
|
|
select: {
|
|
limit: `${widget.widgetName}.pageSize`,
|
|
where: `${widget.widgetName}.searchText`,
|
|
offset: `${widget.widgetName}.pageOffset`,
|
|
orderBy: `${widget.widgetName}.sortOrder.column`,
|
|
sortOrder: `${widget.widgetName}.sortOrder.order !== "desc"`,
|
|
},
|
|
create: {
|
|
value: `(${widget.widgetName}.newRow || {})`,
|
|
},
|
|
update: {
|
|
value: `${widget.widgetName}.updatedRow`,
|
|
where: `${widget.widgetName}.updatedRow`,
|
|
},
|
|
totalRecord: true,
|
|
};
|
|
},
|
|
getPropertyUpdatesForQueryBinding: (
|
|
queryConfig: WidgetQueryConfig,
|
|
_widget: WidgetProps,
|
|
formConfig: WidgetQueryGenerationFormConfig,
|
|
) => {
|
|
const widget = _widget as TableWidgetProps;
|
|
|
|
let modify = {};
|
|
const dynamicPropertyPathList: DynamicPath[] = [];
|
|
|
|
if (queryConfig.select) {
|
|
modify = merge(modify, {
|
|
tableData: queryConfig.select.data,
|
|
onPageChange: queryConfig.select.run,
|
|
serverSidePaginationEnabled: true,
|
|
onSearchTextChanged: formConfig.searchableColumn
|
|
? queryConfig.select.run
|
|
: undefined,
|
|
onSort: queryConfig.select.run,
|
|
enableClientSideSearch: !formConfig.searchableColumn,
|
|
primaryColumnId: formConfig.primaryColumn,
|
|
isVisibleDownload: false,
|
|
});
|
|
}
|
|
|
|
if (queryConfig.create) {
|
|
modify = merge(modify, {
|
|
onAddNewRowSave: queryConfig.create.run,
|
|
allowAddNewRow: true,
|
|
...Object.keys(widget.primaryColumns).reduce(
|
|
(prev: Record<string, boolean>, curr) => {
|
|
if (formConfig.primaryColumn !== curr) {
|
|
prev[`primaryColumns.${curr}.isEditable`] = true;
|
|
prev[`primaryColumns.${curr}.isCellEditable`] = true;
|
|
}
|
|
|
|
prev[`showInlineEditingOptionDropdown`] = true;
|
|
|
|
return prev;
|
|
},
|
|
{},
|
|
),
|
|
});
|
|
}
|
|
|
|
if (queryConfig.update) {
|
|
let editAction = {};
|
|
|
|
if (
|
|
!Object.values(widget.primaryColumns).some(
|
|
(column) => column.columnType === ColumnTypes.EDIT_ACTIONS,
|
|
)
|
|
) {
|
|
editAction = Object.values(createEditActionColumn(widget)).reduce(
|
|
(
|
|
prev: Record<string, unknown>,
|
|
curr: {
|
|
propertyPath: string;
|
|
propertyValue: unknown;
|
|
isDynamicPropertyPath?: boolean;
|
|
},
|
|
) => {
|
|
prev[curr.propertyPath] = curr.propertyValue;
|
|
|
|
if (curr.isDynamicPropertyPath) {
|
|
dynamicPropertyPathList.push({ key: curr.propertyPath });
|
|
}
|
|
|
|
return prev;
|
|
},
|
|
{},
|
|
);
|
|
}
|
|
|
|
modify = merge(modify, {
|
|
...editAction,
|
|
[`primaryColumns.EditActions1.onSave`]: queryConfig.update.run,
|
|
});
|
|
}
|
|
|
|
if (queryConfig.total_record) {
|
|
modify = merge(modify, {
|
|
totalRecordsCount: queryConfig.total_record.data,
|
|
});
|
|
}
|
|
|
|
return {
|
|
modify,
|
|
dynamicUpdates: {
|
|
dynamicPropertyPathList,
|
|
},
|
|
};
|
|
},
|
|
getSnipingModeUpdates: (
|
|
propValueMap: SnipingModeProperty,
|
|
): PropertyUpdates[] => {
|
|
return [
|
|
{
|
|
propertyPath: "tableData",
|
|
propertyValue: propValueMap.data,
|
|
isDynamicPropertyPath: !!propValueMap.isDynamicPropertyPath,
|
|
},
|
|
];
|
|
},
|
|
getOneClickBindingConnectableWidgetConfig: (widget: WidgetProps) => {
|
|
return {
|
|
widgetBindPath: `${widget.widgetName}.selectedRow`,
|
|
message: `Make sure ${widget.widgetName} is bound to the same data source`,
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
static getAutoLayoutConfig() {
|
|
return {
|
|
widgetSize: [
|
|
{
|
|
viewportMinWidth: 0,
|
|
configuration: () => {
|
|
return {
|
|
minWidth: "280px",
|
|
minHeight: "300px",
|
|
};
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
static getAnvilConfig(): AnvilConfig | null {
|
|
return {
|
|
widgetSize: {
|
|
maxHeight: {},
|
|
maxWidth: {},
|
|
minHeight: { base: "300px" },
|
|
minWidth: { base: "280px" },
|
|
},
|
|
};
|
|
}
|
|
|
|
static getPropertyPaneContentConfig() {
|
|
return contentConfig;
|
|
}
|
|
|
|
static getPropertyPaneStyleConfig() {
|
|
return styleConfig;
|
|
}
|
|
|
|
constructor(props: TableWidgetProps) {
|
|
super(props);
|
|
// generate new cache instances so that each table widget instance has its own respective cache instance
|
|
this.memoisedAddNewRow = getMemoisedAddNewRow();
|
|
this.memoiseGetColumnsWithLocalStorage =
|
|
getMemoiseGetColumnsWithLocalStorageFn();
|
|
this.memoiseTransformDataWithEditableCell =
|
|
getMemoiseTransformDataWithEditableCell();
|
|
}
|
|
|
|
static getMetaPropertiesMap(): Record<string, any> {
|
|
return {
|
|
pageNo: 1,
|
|
selectedRowIndex: undefined,
|
|
selectedRowIndices: undefined,
|
|
searchText: undefined,
|
|
triggeredRowIndex: undefined,
|
|
filters: [],
|
|
sortOrder: {
|
|
column: "",
|
|
order: null,
|
|
},
|
|
transientTableData: {},
|
|
updatedRowIndex: -1,
|
|
editableCell: defaultEditableCell,
|
|
columnEditableCellValue: {},
|
|
selectColumnFilterText: {},
|
|
isAddRowInProgress: false,
|
|
newRowContent: undefined,
|
|
newRow: undefined,
|
|
previousPageVisited: false,
|
|
nextPageVisited: false,
|
|
};
|
|
}
|
|
|
|
static getAutocompleteDefinitions(): AutocompletionDefinitions {
|
|
return (widget: TableWidgetProps, extraDefsToDefine?: ExtraDef) => {
|
|
const config: AutocompletionDefinitions = {
|
|
"!doc":
|
|
"The Table is the hero widget of Appsmith. You can display data from an API in a table, trigger an action when a user selects a row and even work with large paginated data sets",
|
|
"!url": "https://docs.appsmith.com/widget-reference/table",
|
|
selectedRow: generateTypeDef(widget.selectedRow, extraDefsToDefine),
|
|
selectedRows: generateTypeDef(widget.selectedRows, extraDefsToDefine),
|
|
selectedRowIndices: generateTypeDef(widget.selectedRowIndices),
|
|
triggeredRow: generateTypeDef(widget.triggeredRow),
|
|
updatedRow: generateTypeDef(widget.updatedRow),
|
|
selectedRowIndex: "number",
|
|
tableData: generateTypeDef(widget.tableData, extraDefsToDefine),
|
|
pageNo: "number",
|
|
pageSize: "number",
|
|
isVisible: DefaultAutocompleteDefinitions.isVisible,
|
|
searchText: "string",
|
|
totalRecordsCount: "number",
|
|
sortOrder: {
|
|
column: "string",
|
|
order: ["asc", "desc"],
|
|
},
|
|
updatedRows: generateTypeDef(widget.updatedRows, extraDefsToDefine),
|
|
updatedRowIndices: generateTypeDef(widget.updatedRowIndices),
|
|
triggeredRowIndex: generateTypeDef(widget.triggeredRowIndex),
|
|
pageOffset: generateTypeDef(widget.pageOffset),
|
|
tableHeaders: generateTypeDef(widget.tableHeaders),
|
|
newRow: generateTypeDef(widget.newRow),
|
|
isAddRowInProgress: "bool",
|
|
previousPageVisited: generateTypeDef(widget.previousPageVisited),
|
|
nextPageVisited: generateTypeDef(widget.nextPageButtonClicked),
|
|
};
|
|
|
|
if (this.getFeatureFlag(ALLOW_TABLE_WIDGET_SERVER_SIDE_FILTERING)) {
|
|
config["filters"] = generateTypeDef(widget.filters);
|
|
}
|
|
|
|
return config;
|
|
};
|
|
}
|
|
|
|
static getDerivedPropertiesMap() {
|
|
return {
|
|
selectedRow: `{{(()=>{${derivedProperties.getSelectedRow}})()}}`,
|
|
triggeredRow: `{{(()=>{${derivedProperties.getTriggeredRow}})()}}`,
|
|
selectedRows: `{{(()=>{${derivedProperties.getSelectedRows}})()}}`,
|
|
pageSize: `{{(()=>{${derivedProperties.getPageSize}})()}}`,
|
|
triggerRowSelection: "{{!!this.onRowSelected}}",
|
|
processedTableData: `{{(()=>{${derivedProperties.getProcessedTableData}})()}}`,
|
|
orderedTableColumns: `{{(()=>{${derivedProperties.getOrderedTableColumns}})()}}`,
|
|
filteredTableData: `{{(()=>{ ${derivedProperties.getFilteredTableData}})()}}`,
|
|
updatedRows: `{{(()=>{ ${derivedProperties.getUpdatedRows}})()}}`,
|
|
updatedRowIndices: `{{(()=>{ ${derivedProperties.getUpdatedRowIndices}})()}}`,
|
|
updatedRow: `{{(()=>{ ${derivedProperties.getUpdatedRow}})()}}`,
|
|
pageOffset: `{{(()=>{${derivedProperties.getPageOffset}})()}}`,
|
|
isEditableCellsValid: `{{(()=>{ ${derivedProperties.getEditableCellValidity}})()}}`,
|
|
tableHeaders: `{{(()=>{${derivedProperties.getTableHeaders}})()}}`,
|
|
};
|
|
}
|
|
|
|
static getDefaultPropertiesMap(): Record<string, string> {
|
|
return {
|
|
searchText: "defaultSearchText",
|
|
selectedRowIndex: "defaultSelectedRowIndex",
|
|
selectedRowIndices: "defaultSelectedRowIndices",
|
|
};
|
|
}
|
|
|
|
static getLoadingProperties(): Array<RegExp> | undefined {
|
|
return [/\.tableData$/];
|
|
}
|
|
|
|
static getStylesheetConfig(): Stylesheet {
|
|
return {
|
|
accentColor: "{{appsmith.theme.colors.primaryColor}}",
|
|
borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
|
boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}",
|
|
childStylesheet: {
|
|
button: {
|
|
buttonColor: "{{appsmith.theme.colors.primaryColor}}",
|
|
borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
|
boxShadow: "none",
|
|
},
|
|
menuButton: {
|
|
menuColor: "{{appsmith.theme.colors.primaryColor}}",
|
|
borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
|
boxShadow: "none",
|
|
},
|
|
iconButton: {
|
|
buttonColor: "{{appsmith.theme.colors.primaryColor}}",
|
|
borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
|
boxShadow: "none",
|
|
},
|
|
editActions: {
|
|
saveButtonColor: "{{appsmith.theme.colors.primaryColor}}",
|
|
saveBorderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
|
discardButtonColor: "{{appsmith.theme.colors.primaryColor}}",
|
|
discardBorderRadius:
|
|
"{{appsmith.theme.borderRadius.appBorderRadius}}",
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
static getSetterConfig(): SetterConfig {
|
|
return {
|
|
__setters: {
|
|
setVisibility: {
|
|
path: "isVisible",
|
|
type: "string",
|
|
},
|
|
setSelectedRowIndex: {
|
|
path: "defaultSelectedRowIndex",
|
|
type: "number",
|
|
disabled: "return options.entity.multiRowSelection",
|
|
},
|
|
setSelectedRowIndices: {
|
|
path: "defaultSelectedRowIndices",
|
|
type: "array",
|
|
disabled: "return !options.entity.multiRowSelection",
|
|
},
|
|
setData: {
|
|
path: "tableData",
|
|
type: "object",
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
/*
|
|
* Function to get the table columns with appropriate render functions
|
|
* based on columnType
|
|
*/
|
|
getTableColumns = () => {
|
|
const { columnWidthMap, orderedTableColumns, renderMode, widgetId } =
|
|
this.props;
|
|
const { componentWidth } = this.getPaddingAdjustedDimensions();
|
|
const widgetLocalStorageState = getColumnOrderByWidgetIdFromLS(widgetId);
|
|
const memoisdGetColumnsWithLocalStorage =
|
|
this.memoiseGetColumnsWithLocalStorage(widgetLocalStorageState);
|
|
return memoisdGetColumnsWithLocalStorage(
|
|
this.renderCell,
|
|
columnWidthMap,
|
|
orderedTableColumns,
|
|
componentWidth,
|
|
renderMode,
|
|
);
|
|
};
|
|
|
|
transformData = (
|
|
tableData: Array<Record<string, unknown>>,
|
|
columns: ReactTableColumnProps[],
|
|
) => {
|
|
return this.memoiseTransformDataWithEditableCell(
|
|
this.props.editableCell,
|
|
tableData,
|
|
columns,
|
|
);
|
|
};
|
|
|
|
updateDerivedColumnsIndex = (
|
|
derivedColumns: Record<string, ColumnProperties>,
|
|
tableColumnCount: number,
|
|
) => {
|
|
if (!derivedColumns) {
|
|
return [];
|
|
}
|
|
|
|
//update index property of all columns in new derived columns
|
|
return Object.values(derivedColumns).map(
|
|
(column: ColumnProperties, index: number) => {
|
|
return {
|
|
...column,
|
|
index: index + tableColumnCount,
|
|
};
|
|
},
|
|
);
|
|
};
|
|
|
|
/*
|
|
* Function to create new primary Columns from the tableData
|
|
* gets called on component mount and on component update
|
|
*/
|
|
createTablePrimaryColumns = ():
|
|
| Record<string, ColumnProperties>
|
|
| undefined => {
|
|
const { primaryColumns = {}, tableData = [] } = this.props;
|
|
|
|
if (!_.isArray(tableData) || tableData.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const existingColumnIds = Object.keys(primaryColumns);
|
|
const newTableColumns: Record<string, ColumnProperties> = {};
|
|
const tableStyles = getTableStyles(this.props);
|
|
const columnKeys: string[] = getAllTableColumnKeys(tableData);
|
|
|
|
/*
|
|
* Generate default column properties for all columns
|
|
* But do not replace existing columns with the same id
|
|
*/
|
|
columnKeys.forEach((columnKey, index) => {
|
|
const existingColumn = this.getColumnByOriginalId(columnKey);
|
|
|
|
if (!!existingColumn) {
|
|
// Use the existing column properties
|
|
newTableColumns[existingColumn.id] = existingColumn;
|
|
} else {
|
|
const hashedColumnKey = sanitizeKey(columnKey, {
|
|
existingKeys: union(existingColumnIds, Object.keys(newTableColumns)),
|
|
});
|
|
// Create column properties for the new column
|
|
const columnType = getColumnType(tableData, columnKey);
|
|
const columnProperties = getDefaultColumnProperties(
|
|
columnKey,
|
|
hashedColumnKey,
|
|
index,
|
|
this.props.widgetName,
|
|
false,
|
|
columnType,
|
|
);
|
|
|
|
newTableColumns[columnProperties.id] = {
|
|
...columnProperties,
|
|
...tableStyles,
|
|
};
|
|
}
|
|
});
|
|
|
|
const derivedColumns: Record<string, ColumnProperties> =
|
|
getDerivedColumns(primaryColumns);
|
|
|
|
const updatedDerivedColumns = this.updateDerivedColumnsIndex(
|
|
derivedColumns,
|
|
Object.keys(newTableColumns).length,
|
|
);
|
|
|
|
//add derived columns to new Table columns
|
|
updatedDerivedColumns.forEach((derivedColumn: ColumnProperties) => {
|
|
newTableColumns[derivedColumn.id] = derivedColumn;
|
|
});
|
|
|
|
const newColumnIds = Object.keys(newTableColumns);
|
|
|
|
// check if the columns ids differ
|
|
if (_.xor(existingColumnIds, newColumnIds).length > 0) {
|
|
return newTableColumns;
|
|
} else {
|
|
return;
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Function to update primaryColumns when the tablData schema changes
|
|
*/
|
|
updateColumnProperties = (
|
|
tableColumns?: Record<string, ColumnProperties>,
|
|
shouldPersistLocalOrderWhenTableDataChanges = false,
|
|
) => {
|
|
const { columnOrder = [], primaryColumns = {} } = this.props;
|
|
const derivedColumns = getDerivedColumns(primaryColumns);
|
|
|
|
if (tableColumns) {
|
|
const existingColumnIds = Object.keys(primaryColumns);
|
|
const existingDerivedColumnIds = Object.keys(derivedColumns);
|
|
|
|
const newColumnIds = Object.keys(tableColumns);
|
|
|
|
//Check if there is any difference in the existing and new columns ids
|
|
if (_.xor(existingColumnIds, newColumnIds).length > 0) {
|
|
const newColumnIdsToAdd = _.without(newColumnIds, ...existingColumnIds);
|
|
|
|
const propertiesToAdd: Record<string, unknown> = {};
|
|
|
|
newColumnIdsToAdd.forEach((columnId: string) => {
|
|
// id could be an empty string
|
|
if (!!columnId) {
|
|
Object.entries(tableColumns[columnId]).forEach(([key, value]) => {
|
|
propertiesToAdd[`primaryColumns.${columnId}.${key}`] = value;
|
|
});
|
|
}
|
|
});
|
|
|
|
/*
|
|
* If new columnOrders have different values from the original columnOrders
|
|
* Only update when there are new Columns(Derived or Primary)
|
|
*/
|
|
if (
|
|
!!newColumnIds.length &&
|
|
!!_.xor(newColumnIds, columnOrder).length &&
|
|
!equal(_.sortBy(newColumnIds), _.sortBy(existingDerivedColumnIds))
|
|
) {
|
|
// Maintain original columnOrder and keep new columns at the end
|
|
let newColumnOrder = _.intersection(columnOrder, newColumnIds);
|
|
newColumnOrder = _.union(newColumnOrder, newColumnIds);
|
|
|
|
const compareColumns = (a: string, b: string) => {
|
|
const aSticky = tableColumns[a].sticky || "none";
|
|
const bSticky = tableColumns[b].sticky || "none";
|
|
|
|
if (aSticky === bSticky) {
|
|
return 0;
|
|
}
|
|
|
|
return SORT_ORDER[aSticky] - SORT_ORDER[bSticky];
|
|
};
|
|
|
|
// Sort the column order to retain the position of frozen columns
|
|
newColumnOrder.sort(compareColumns);
|
|
|
|
propertiesToAdd["columnOrder"] = newColumnOrder;
|
|
|
|
/**
|
|
* As the table data changes in Deployed app, we also update the local storage.
|
|
*
|
|
* this.updateColumnProperties gets executed on mount and on update of the component.
|
|
* On mount we get new tableColumns that may not have any sticky columns.
|
|
* This will lead to loss of sticky column that were frozen by the user.
|
|
* To avoid this and to maintain user's sticky columns we use shouldPersistLocalOrderWhenTableDataChanges below
|
|
* so as to avoid updating the local storage on mount.
|
|
**/
|
|
if (
|
|
this.props.renderMode === RenderModes.PAGE &&
|
|
shouldPersistLocalOrderWhenTableDataChanges
|
|
) {
|
|
const leftOrder = newColumnOrder.filter(
|
|
(col: string) => tableColumns[col].sticky === StickyType.LEFT,
|
|
);
|
|
const rightOrder = newColumnOrder.filter(
|
|
(col: string) => tableColumns[col].sticky === StickyType.RIGHT,
|
|
);
|
|
this.persistColumnOrder(newColumnOrder, leftOrder, rightOrder);
|
|
}
|
|
}
|
|
|
|
const propertiesToUpdate: BatchPropertyUpdatePayload = {
|
|
modify: propertiesToAdd,
|
|
};
|
|
|
|
const pathsToDelete: string[] = [];
|
|
const columnsIdsToDelete = without(existingColumnIds, ...newColumnIds);
|
|
|
|
if (!!columnsIdsToDelete.length) {
|
|
columnsIdsToDelete.forEach((id: string) => {
|
|
if (!primaryColumns[id].isDerived) {
|
|
pathsToDelete.push(`primaryColumns.${id}`);
|
|
}
|
|
});
|
|
propertiesToUpdate.remove = pathsToDelete;
|
|
}
|
|
|
|
super.batchUpdateWidgetProperty(propertiesToUpdate, false);
|
|
}
|
|
}
|
|
};
|
|
|
|
//no need to batch meta updates
|
|
hydrateStickyColumns = () => {
|
|
const localTableColumnOrder = getColumnOrderByWidgetIdFromLS(
|
|
this.props.widgetId,
|
|
);
|
|
const leftLen: number = Object.keys(
|
|
pickBy(this.props.primaryColumns, (col) => col.sticky === "left"),
|
|
).length;
|
|
|
|
const leftOrder = [...(this.props.columnOrder || [])].slice(0, leftLen);
|
|
|
|
const rightLen: number = Object.keys(
|
|
pickBy(this.props.primaryColumns, (col) => col.sticky !== "right"),
|
|
).length;
|
|
|
|
const rightOrder: string[] = [...(this.props.columnOrder || [])].slice(
|
|
rightLen,
|
|
);
|
|
|
|
if (localTableColumnOrder) {
|
|
const {
|
|
columnOrder,
|
|
columnUpdatedAt,
|
|
leftOrder: localLeftOrder,
|
|
rightOrder: localRightOrder,
|
|
} = localTableColumnOrder;
|
|
|
|
if (this.props.columnUpdatedAt !== columnUpdatedAt) {
|
|
// Delete and set the column orders defined by the developer
|
|
deleteLocalTableColumnOrderByWidgetId(this.props.widgetId);
|
|
|
|
this.persistColumnOrder(
|
|
this.props.columnOrder ?? [],
|
|
leftOrder,
|
|
rightOrder,
|
|
);
|
|
} else {
|
|
const propertiesToAdd: Record<string, string> = {};
|
|
|
|
propertiesToAdd["columnOrder"] = columnOrder;
|
|
|
|
/**
|
|
* We reset the sticky values of the columns that were frozen by the developer.
|
|
*/
|
|
if (Object.keys(this.props.primaryColumns).length > 0) {
|
|
columnOrder.forEach((colName: string) => {
|
|
if (
|
|
this.props.primaryColumns[colName]?.sticky !== StickyType.NONE
|
|
) {
|
|
propertiesToAdd[`primaryColumns.${colName}.sticky`] =
|
|
StickyType.NONE;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* We pickup the left and the right frozen columns from the localstorage
|
|
* and update the sticky value of these columns respectively.
|
|
*/
|
|
|
|
if (localLeftOrder.length > 0) {
|
|
localLeftOrder.forEach((colName: string) => {
|
|
propertiesToAdd[`primaryColumns.${colName}.sticky`] =
|
|
StickyType.LEFT;
|
|
});
|
|
}
|
|
|
|
if (localRightOrder.length > 0) {
|
|
localRightOrder.forEach((colName: string) => {
|
|
propertiesToAdd[`primaryColumns.${colName}.sticky`] =
|
|
StickyType.RIGHT;
|
|
});
|
|
}
|
|
|
|
const propertiesToUpdate = {
|
|
modify: propertiesToAdd,
|
|
};
|
|
super.batchUpdateWidgetProperty(propertiesToUpdate);
|
|
}
|
|
} else {
|
|
// If user deletes local storage or no column orders for the given table widget exists hydrate it with the developer changes.
|
|
this.persistColumnOrder(
|
|
this.props.columnOrder ?? [],
|
|
leftOrder,
|
|
rightOrder,
|
|
);
|
|
}
|
|
};
|
|
|
|
componentDidMount() {
|
|
const { canFreezeColumn, renderMode, tableData } = this.props;
|
|
|
|
if (_.isArray(tableData) && !!tableData.length) {
|
|
const newPrimaryColumns = this.createTablePrimaryColumns();
|
|
|
|
// When the Table data schema changes
|
|
if (newPrimaryColumns && !!Object.keys(newPrimaryColumns).length) {
|
|
this.updateColumnProperties(newPrimaryColumns);
|
|
}
|
|
}
|
|
|
|
if (canFreezeColumn && renderMode === RenderModes.PAGE) {
|
|
//dont neet to batch this since single action
|
|
this.hydrateStickyColumns();
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps: TableWidgetProps) {
|
|
const {
|
|
defaultSelectedRowIndex,
|
|
defaultSelectedRowIndices,
|
|
pageNo,
|
|
pageSize,
|
|
primaryColumns = {},
|
|
serverSidePaginationEnabled,
|
|
totalRecordsCount,
|
|
} = this.props;
|
|
|
|
// Bail out if tableData is a string. This signifies an error in evaluations
|
|
if (isString(this.props.tableData)) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
this.props.primaryColumns &&
|
|
(!equal(prevProps.columnOrder, this.props.columnOrder) ||
|
|
filter(prevProps.orderedTableColumns, { isVisible: false }).length !==
|
|
filter(this.props.orderedTableColumns, { isVisible: false }).length ||
|
|
getAllStickyColumnsCount(prevProps.orderedTableColumns) !==
|
|
getAllStickyColumnsCount(this.props.orderedTableColumns))
|
|
) {
|
|
if (this.props.renderMode === RenderModes.CANVAS) {
|
|
super.batchUpdateWidgetProperty(
|
|
{
|
|
modify: {
|
|
columnUpdatedAt: Date.now(),
|
|
},
|
|
},
|
|
false,
|
|
);
|
|
}
|
|
}
|
|
|
|
//check if necessary we are batching now updates
|
|
// Check if tableData is modifed
|
|
const isTableDataModified = !equal(
|
|
this.props.tableData,
|
|
prevProps.tableData,
|
|
);
|
|
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
// If the user has changed the tableData OR
|
|
// The binding has returned a new value
|
|
if (isTableDataModified) {
|
|
this.pushMetaRowDataUpdates(
|
|
prevProps.filteredTableData,
|
|
this.props.filteredTableData,
|
|
);
|
|
|
|
pushBatchMetaUpdates("triggeredRowIndex", -1);
|
|
|
|
const newColumnIds: string[] = getAllTableColumnKeys(
|
|
this.props.tableData,
|
|
);
|
|
const primaryColumnIds = Object.keys(primaryColumns).filter(
|
|
(id: string) => !primaryColumns[id].isDerived,
|
|
);
|
|
|
|
if (xor(newColumnIds, primaryColumnIds).length > 0) {
|
|
const newTableColumns = this.createTablePrimaryColumns();
|
|
|
|
if (newTableColumns) {
|
|
this.updateColumnProperties(newTableColumns, isTableDataModified);
|
|
}
|
|
|
|
pushBatchMetaUpdates("filters", []);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clear transient table data and editablecell when tableData changes
|
|
*/
|
|
if (isTableDataModified) {
|
|
pushBatchMetaUpdates("transientTableData", {});
|
|
// reset updatedRowIndex whenever transientTableData is flushed.
|
|
pushBatchMetaUpdates("updatedRowIndex", -1);
|
|
|
|
this.pushClearEditableCellsUpdates();
|
|
pushBatchMetaUpdates("selectColumnFilterText", {});
|
|
}
|
|
|
|
if (!pageNo) {
|
|
pushBatchMetaUpdates("pageNo", 1);
|
|
this.updatePaginationDirectionFlags(PaginationDirection.INITIAL);
|
|
}
|
|
|
|
//check if pageNo does not excede the max Page no, due to change of totalRecordsCount
|
|
if (serverSidePaginationEnabled !== prevProps.serverSidePaginationEnabled) {
|
|
//reset pageNo when serverSidePaginationEnabled is toggled
|
|
pushBatchMetaUpdates("pageNo", 1);
|
|
this.updatePaginationDirectionFlags(PaginationDirection.INITIAL);
|
|
} else {
|
|
//check if pageNo does not excede the max Page no, due to change of totalRecordsCount or change of pageSize
|
|
if (serverSidePaginationEnabled && totalRecordsCount) {
|
|
const maxAllowedPageNumber = Math.ceil(totalRecordsCount / pageSize);
|
|
|
|
if (pageNo > maxAllowedPageNumber) {
|
|
pushBatchMetaUpdates("pageNo", maxAllowedPageNumber);
|
|
this.updatePaginationDirectionFlags(PaginationDirection.NEXT_PAGE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* When defaultSelectedRowIndex or defaultSelectedRowIndices
|
|
* is changed from property pane
|
|
*/
|
|
if (
|
|
!equal(defaultSelectedRowIndex, prevProps.defaultSelectedRowIndex) ||
|
|
!equal(defaultSelectedRowIndices, prevProps.defaultSelectedRowIndices)
|
|
) {
|
|
this.pushUpdateSelectedRowIndexUpdates();
|
|
}
|
|
|
|
this.pushResetPageNoUpdates(prevProps);
|
|
|
|
this.pushResetRowSelectionPropertiesUpdates(prevProps);
|
|
commitBatchMetaUpdates();
|
|
}
|
|
|
|
pushResetPageNoUpdates = (prevProps: TableWidgetProps) => {
|
|
const { onPageSizeChange, pageSize, pushBatchMetaUpdates } = this.props;
|
|
|
|
if (pageSize !== prevProps.pageSize) {
|
|
if (onPageSizeChange) {
|
|
this.updatePaginationDirectionFlags(PaginationDirection.INITIAL);
|
|
pushBatchMetaUpdates("pageNo", 1, {
|
|
triggerPropertyName: "onPageSizeChange",
|
|
dynamicString: onPageSizeChange,
|
|
event: {
|
|
type: EventType.ON_PAGE_SIZE_CHANGE,
|
|
},
|
|
});
|
|
} else {
|
|
pushBatchMetaUpdates("pageNo", 1);
|
|
this.updatePaginationDirectionFlags(PaginationDirection.INITIAL);
|
|
}
|
|
}
|
|
};
|
|
|
|
pushResetRowSelectionPropertiesUpdates = (prevProps: TableWidgetProps) => {
|
|
const {
|
|
defaultSelectedRowIndex,
|
|
defaultSelectedRowIndices,
|
|
multiRowSelection,
|
|
pushBatchMetaUpdates,
|
|
} = this.props;
|
|
|
|
// reset selectedRowIndices and selectedRowIndex to defaults
|
|
if (multiRowSelection !== prevProps.multiRowSelection) {
|
|
if (multiRowSelection) {
|
|
if (
|
|
defaultSelectedRowIndices &&
|
|
_.isArray(defaultSelectedRowIndices) &&
|
|
defaultSelectedRowIndices.every((i) => _.isFinite(i))
|
|
) {
|
|
pushBatchMetaUpdates("selectedRowIndices", defaultSelectedRowIndices);
|
|
}
|
|
|
|
pushBatchMetaUpdates("selectedRowIndex", -1);
|
|
} else {
|
|
if (
|
|
!isNil(defaultSelectedRowIndex) &&
|
|
parseInt(defaultSelectedRowIndex?.toString(), 10) > -1
|
|
) {
|
|
pushBatchMetaUpdates("selectedRowIndex", defaultSelectedRowIndex);
|
|
}
|
|
|
|
pushBatchMetaUpdates("selectedRowIndices", []);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Function to update selectedRowIndices & selectedRowIndex from
|
|
* defaultSelectedRowIndices & defaultSelectedRowIndex respectively
|
|
*/
|
|
pushUpdateSelectedRowIndexUpdates = () => {
|
|
const {
|
|
defaultSelectedRowIndex,
|
|
defaultSelectedRowIndices,
|
|
multiRowSelection,
|
|
pushBatchMetaUpdates,
|
|
} = this.props;
|
|
|
|
if (multiRowSelection) {
|
|
pushBatchMetaUpdates("selectedRowIndices", defaultSelectedRowIndices);
|
|
} else {
|
|
pushBatchMetaUpdates("selectedRowIndex", defaultSelectedRowIndex);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Function to update selectedRow details when order of tableData changes
|
|
*/
|
|
pushMetaRowDataUpdates = (
|
|
oldTableData: Array<Record<string, unknown>>,
|
|
newTableData: Array<Record<string, unknown>>,
|
|
) => {
|
|
const {
|
|
defaultSelectedRowIndex,
|
|
defaultSelectedRowIndices,
|
|
multiRowSelection,
|
|
primaryColumnId,
|
|
pushBatchMetaUpdates,
|
|
selectedRowIndex,
|
|
selectedRowIndices,
|
|
} = this.props;
|
|
|
|
if (multiRowSelection) {
|
|
const indices = getSelectRowIndices(
|
|
oldTableData,
|
|
newTableData,
|
|
defaultSelectedRowIndices,
|
|
selectedRowIndices,
|
|
primaryColumnId,
|
|
);
|
|
pushBatchMetaUpdates("selectedRowIndices", indices);
|
|
} else {
|
|
const index = getSelectRowIndex(
|
|
oldTableData,
|
|
newTableData,
|
|
defaultSelectedRowIndex,
|
|
selectedRowIndex,
|
|
primaryColumnId,
|
|
);
|
|
pushBatchMetaUpdates("selectedRowIndex", index);
|
|
}
|
|
};
|
|
|
|
getSelectedRowIndices = () => {
|
|
const { multiRowSelection, selectedRowIndices } = this.props;
|
|
|
|
let indices: number[] | undefined;
|
|
|
|
if (multiRowSelection) {
|
|
if (_.isArray(selectedRowIndices)) {
|
|
indices = selectedRowIndices;
|
|
} else if (_.isNumber(selectedRowIndices)) {
|
|
indices = [selectedRowIndices];
|
|
} else {
|
|
indices = [];
|
|
}
|
|
} else {
|
|
indices = undefined;
|
|
}
|
|
|
|
return indices;
|
|
};
|
|
|
|
updateFilters = (filters: ReactTableFilter[]) => {
|
|
const {
|
|
commitBatchMetaUpdates,
|
|
enableServerSideFiltering,
|
|
onTableFilterUpdate,
|
|
pushBatchMetaUpdates,
|
|
} = this.props;
|
|
|
|
this.pushResetSelectedRowIndexUpdates();
|
|
|
|
if (enableServerSideFiltering) {
|
|
pushBatchMetaUpdates("filters", filters, {
|
|
triggerPropertyName: "onTableFilterUpdate",
|
|
dynamicString: onTableFilterUpdate,
|
|
event: {
|
|
type: EventType.ON_FILTER_UPDATE,
|
|
},
|
|
});
|
|
} else {
|
|
pushBatchMetaUpdates("filters", filters);
|
|
}
|
|
|
|
// Reset Page only when a filter is added
|
|
if (!isEmpty(xorWith(filters, [DEFAULT_FILTER], equal))) {
|
|
pushBatchMetaUpdates("pageNo", 1);
|
|
this.updatePaginationDirectionFlags(PaginationDirection.INITIAL);
|
|
}
|
|
commitBatchMetaUpdates();
|
|
};
|
|
|
|
toggleDrag = (disable: boolean) => {
|
|
this.disableDrag(disable);
|
|
};
|
|
|
|
getPaddingAdjustedDimensions = () => {
|
|
// eslint-disable-next-line prefer-const
|
|
let { componentHeight, componentWidth } = this.props;
|
|
// (2 * WIDGET_PADDING) gives the total horizontal padding (i.e. paddingLeft + paddingRight)
|
|
componentWidth = componentWidth - 2 * WIDGET_PADDING;
|
|
return { componentHeight, componentWidth };
|
|
};
|
|
|
|
getWidgetView() {
|
|
const {
|
|
delimiter,
|
|
filteredTableData = [],
|
|
isVisibleDownload,
|
|
isVisibleFilters,
|
|
isVisiblePagination,
|
|
isVisibleSearch,
|
|
pageSize,
|
|
primaryColumns,
|
|
totalRecordsCount,
|
|
} = this.props;
|
|
|
|
const tableColumns = this.getTableColumns() || emptyArr;
|
|
const transformedData = this.transformData(filteredTableData, tableColumns);
|
|
const isVisibleHeaderOptions =
|
|
isVisibleDownload ||
|
|
isVisibleFilters ||
|
|
isVisiblePagination ||
|
|
isVisibleSearch;
|
|
|
|
const { componentHeight, componentWidth } =
|
|
this.getPaddingAdjustedDimensions();
|
|
const finalTableData = this.memoisedAddNewRow(
|
|
transformedData,
|
|
this.props.isAddRowInProgress,
|
|
this.props.newRowContent,
|
|
);
|
|
|
|
return (
|
|
<Suspense fallback={<Skeleton />}>
|
|
<ReactTableComponent
|
|
accentColor={this.props.accentColor}
|
|
allowAddNewRow={this.props.allowAddNewRow}
|
|
allowRowSelection={!this.props.isAddRowInProgress}
|
|
allowSorting={!this.props.isAddRowInProgress}
|
|
applyFilter={this.updateFilters}
|
|
borderColor={this.props.borderColor}
|
|
borderRadius={this.props.borderRadius}
|
|
borderWidth={this.props.borderWidth}
|
|
boxShadow={this.props.boxShadow}
|
|
canFreezeColumn={this.props.canFreezeColumn}
|
|
columnWidthMap={this.props.columnWidthMap}
|
|
columns={tableColumns}
|
|
compactMode={this.props.compactMode || CompactModeTypes.DEFAULT}
|
|
delimiter={delimiter}
|
|
disableDrag={this.toggleDrag}
|
|
disabledAddNewRowSave={this.hasInvalidColumnCell()}
|
|
editMode={this.props.renderMode === RenderModes.CANVAS}
|
|
editableCell={this.props.editableCell}
|
|
filters={this.props.filters}
|
|
handleColumnFreeze={this.handleColumnFreeze}
|
|
handleReorderColumn={this.handleReorderColumn}
|
|
handleResizeColumn={this.handleResizeColumn}
|
|
height={componentHeight}
|
|
isAddRowInProgress={this.props.isAddRowInProgress}
|
|
isEditableCellsValid={this.props.isEditableCellsValid}
|
|
isLoading={this.props.isLoading}
|
|
isSortable={this.props.isSortable ?? true}
|
|
isVisibleDownload={isVisibleDownload}
|
|
isVisibleFilters={isVisibleFilters}
|
|
isVisiblePagination={isVisiblePagination}
|
|
isVisibleSearch={isVisibleSearch}
|
|
multiRowSelection={
|
|
this.props.multiRowSelection && !this.props.isAddRowInProgress
|
|
}
|
|
nextPageClick={this.handleNextPageClick}
|
|
onAddNewRow={this.handleAddNewRowClick}
|
|
onAddNewRowAction={this.handleAddNewRowAction}
|
|
onBulkEditDiscard={this.onBulkEditDiscard}
|
|
onBulkEditSave={this.onBulkEditSave}
|
|
onConnectData={this.onConnectData}
|
|
onRowClick={this.handleRowClick}
|
|
pageNo={this.props.pageNo}
|
|
pageSize={
|
|
isVisibleHeaderOptions ? Math.max(1, pageSize) : pageSize + 1
|
|
}
|
|
prevPageClick={this.handlePrevPageClick}
|
|
primaryColumnId={this.props.primaryColumnId}
|
|
searchKey={this.props.searchText}
|
|
searchTableData={this.handleSearchTable}
|
|
selectAllRow={this.handleAllRowSelect}
|
|
selectedRowIndex={
|
|
this.props.selectedRowIndex === undefined
|
|
? -1
|
|
: this.props.selectedRowIndex
|
|
}
|
|
selectedRowIndices={this.getSelectedRowIndices()}
|
|
serverSidePaginationEnabled={!!this.props.serverSidePaginationEnabled}
|
|
showConnectDataOverlay={
|
|
primaryColumns &&
|
|
!Object.keys(primaryColumns).length &&
|
|
this.props.renderMode === RenderModes.CANVAS
|
|
}
|
|
sortTableColumn={this.handleColumnSorting}
|
|
tableData={finalTableData}
|
|
totalRecordsCount={totalRecordsCount}
|
|
triggerRowSelection={this.props.triggerRowSelection}
|
|
unSelectAllRow={this.unSelectAllRow}
|
|
updatePageNo={this.updatePageNumber}
|
|
variant={this.props.variant}
|
|
widgetId={this.props.widgetId}
|
|
widgetName={this.props.widgetName}
|
|
width={componentWidth}
|
|
/>
|
|
</Suspense>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Function to update or add the tableWidgetColumnOrder key in the local storage
|
|
* tableWidgetColumnOrder = {
|
|
* <widget-id>: {
|
|
* columnOrder: [],
|
|
* leftOrder: [],
|
|
* rightOrder: [],
|
|
* }
|
|
* }
|
|
*/
|
|
persistColumnOrder = (
|
|
newColumnOrder: string[],
|
|
leftOrder: string[],
|
|
rightOrder: string[],
|
|
) => {
|
|
const widgetId = this.props.widgetId;
|
|
const localTableWidgetColumnOrder = localStorage.getItem(
|
|
TABLE_COLUMN_ORDER_KEY,
|
|
);
|
|
let newTableColumnOrder;
|
|
|
|
if (localTableWidgetColumnOrder) {
|
|
try {
|
|
let parsedTableWidgetColumnOrder = JSON.parse(
|
|
localTableWidgetColumnOrder,
|
|
);
|
|
|
|
let columnOrder;
|
|
|
|
if (newColumnOrder) {
|
|
columnOrder = newColumnOrder;
|
|
} else if (parsedTableWidgetColumnOrder[widgetId]) {
|
|
columnOrder = parsedTableWidgetColumnOrder[widgetId];
|
|
} else {
|
|
columnOrder = this.props.columnOrder;
|
|
}
|
|
|
|
parsedTableWidgetColumnOrder = {
|
|
...parsedTableWidgetColumnOrder,
|
|
[widgetId]: {
|
|
columnOrder,
|
|
columnUpdatedAt: this.props.columnUpdatedAt,
|
|
leftOrder,
|
|
rightOrder,
|
|
},
|
|
};
|
|
|
|
newTableColumnOrder = parsedTableWidgetColumnOrder;
|
|
} catch (e) {
|
|
log.debug("Unable to parse local column order:", { e });
|
|
}
|
|
} else {
|
|
const tableWidgetColumnOrder = {
|
|
[widgetId]: {
|
|
columnOrder: newColumnOrder,
|
|
columnUpdatedAt: this.props.columnUpdatedAt,
|
|
leftOrder,
|
|
rightOrder,
|
|
},
|
|
};
|
|
newTableColumnOrder = tableWidgetColumnOrder;
|
|
}
|
|
localStorage.setItem(
|
|
TABLE_COLUMN_ORDER_KEY,
|
|
JSON.stringify(newTableColumnOrder),
|
|
);
|
|
};
|
|
|
|
handleColumnFreeze = (columnName: string, sticky?: StickyType) => {
|
|
if (this.props.columnOrder) {
|
|
let newColumnOrder;
|
|
const localTableColumnOrder = getColumnOrderByWidgetIdFromLS(
|
|
this.props.widgetId,
|
|
);
|
|
if (this.props.renderMode === RenderModes.CANVAS) {
|
|
newColumnOrder = generateNewColumnOrderFromStickyValue(
|
|
this.props.primaryColumns,
|
|
this.props.columnOrder,
|
|
columnName,
|
|
sticky,
|
|
);
|
|
|
|
// Updating these properties in batch so that undo/redo gets executed in a combined way.
|
|
super.batchUpdateWidgetProperty(
|
|
{
|
|
modify: {
|
|
[`primaryColumns.${columnName}.sticky`]: sticky,
|
|
columnOrder: newColumnOrder,
|
|
},
|
|
},
|
|
true,
|
|
);
|
|
} else if (
|
|
localTableColumnOrder &&
|
|
this.props.renderMode === RenderModes.PAGE
|
|
) {
|
|
const { leftOrder, rightOrder } = localTableColumnOrder;
|
|
newColumnOrder = generateLocalNewColumnOrderFromStickyValue(
|
|
localTableColumnOrder.columnOrder,
|
|
columnName,
|
|
sticky,
|
|
leftOrder,
|
|
rightOrder,
|
|
);
|
|
const updatedOrders = updateAndSyncTableLocalColumnOrders(
|
|
columnName,
|
|
leftOrder,
|
|
rightOrder,
|
|
sticky,
|
|
);
|
|
this.persistColumnOrder(
|
|
newColumnOrder,
|
|
updatedOrders.leftOrder,
|
|
updatedOrders.rightOrder,
|
|
);
|
|
|
|
super.batchUpdateWidgetProperty(
|
|
{
|
|
modify: {
|
|
[`primaryColumns.${columnName}.sticky`]: sticky,
|
|
columnOrder: newColumnOrder,
|
|
},
|
|
},
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
handleReorderColumn = (columnOrder: string[]) => {
|
|
columnOrder = columnOrder.map((alias) => this.getColumnIdByAlias(alias));
|
|
|
|
if (
|
|
this.props.canFreezeColumn &&
|
|
this.props.renderMode === RenderModes.PAGE
|
|
) {
|
|
const localTableColumnOrder = getColumnOrderByWidgetIdFromLS(
|
|
this.props.widgetId,
|
|
);
|
|
if (localTableColumnOrder) {
|
|
const { leftOrder, rightOrder } = localTableColumnOrder;
|
|
this.persistColumnOrder(columnOrder, leftOrder, rightOrder);
|
|
} else {
|
|
this.persistColumnOrder(columnOrder, [], []);
|
|
}
|
|
}
|
|
|
|
super.updateWidgetProperty("columnOrder", columnOrder);
|
|
};
|
|
|
|
handleColumnSorting = (columnAccessor: string, isAsc: boolean) => {
|
|
const columnId = this.getColumnIdByAlias(columnAccessor);
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
this.pushResetSelectedRowIndexUpdates(false);
|
|
|
|
let sortOrderProps;
|
|
|
|
if (columnId) {
|
|
sortOrderProps = {
|
|
column: columnId,
|
|
order: isAsc ? SortOrderTypes.asc : SortOrderTypes.desc,
|
|
};
|
|
} else {
|
|
sortOrderProps = {
|
|
column: "",
|
|
order: null,
|
|
};
|
|
}
|
|
|
|
pushBatchMetaUpdates("sortOrder", sortOrderProps, {
|
|
triggerPropertyName: "onSort",
|
|
dynamicString: this.props.onSort,
|
|
event: {
|
|
type: EventType.ON_SORT,
|
|
},
|
|
});
|
|
commitBatchMetaUpdates();
|
|
};
|
|
|
|
handleResizeColumn = (columnWidthMap: { [key: string]: number }) => {
|
|
if (this.props.renderMode === RenderModes.CANVAS) {
|
|
super.updateWidgetProperty("columnWidthMap", columnWidthMap);
|
|
} else {
|
|
//single action no need to batch
|
|
this.props.updateWidgetMetaProperty("columnWidthMap", columnWidthMap);
|
|
}
|
|
};
|
|
|
|
handleSearchTable = (searchKey: any) => {
|
|
const {
|
|
commitBatchMetaUpdates,
|
|
multiRowSelection,
|
|
onSearchTextChanged,
|
|
pushBatchMetaUpdates,
|
|
} = this.props;
|
|
|
|
/*
|
|
* Clear rowSelection to avoid selecting filtered rows
|
|
* based on stale selection indices
|
|
*/
|
|
if (multiRowSelection) {
|
|
pushBatchMetaUpdates("selectedRowIndices", []);
|
|
} else {
|
|
pushBatchMetaUpdates("selectedRowIndex", -1);
|
|
}
|
|
|
|
pushBatchMetaUpdates("pageNo", 1);
|
|
this.updatePaginationDirectionFlags(PaginationDirection.INITIAL);
|
|
|
|
pushBatchMetaUpdates("searchText", searchKey, {
|
|
triggerPropertyName: "onSearchTextChanged",
|
|
dynamicString: onSearchTextChanged,
|
|
event: {
|
|
type: EventType.ON_SEARCH,
|
|
},
|
|
});
|
|
|
|
commitBatchMetaUpdates();
|
|
};
|
|
|
|
/**
|
|
* This function just pushes the meta update
|
|
*/
|
|
pushOnColumnEvent = ({
|
|
action,
|
|
additionalData = {},
|
|
eventType,
|
|
onComplete = noop,
|
|
row,
|
|
rowIndex,
|
|
triggerPropertyName,
|
|
}: OnColumnEventArgs) => {
|
|
const { filteredTableData = [], pushBatchMetaUpdates } = this.props;
|
|
|
|
const currentRow = row || filteredTableData[rowIndex];
|
|
pushBatchMetaUpdates(
|
|
"triggeredRowIndex",
|
|
currentRow?.[ORIGINAL_INDEX_KEY],
|
|
{
|
|
triggerPropertyName: triggerPropertyName,
|
|
dynamicString: action,
|
|
event: {
|
|
type: eventType,
|
|
callback: onComplete,
|
|
},
|
|
globalContext: { currentRow, ...additionalData },
|
|
},
|
|
);
|
|
};
|
|
/*
|
|
* Function to handle customColumn button type click interactions
|
|
*/
|
|
onColumnEvent = ({
|
|
action,
|
|
additionalData = {},
|
|
eventType,
|
|
onComplete = noop,
|
|
row,
|
|
rowIndex,
|
|
triggerPropertyName,
|
|
}: OnColumnEventArgs) => {
|
|
if (action) {
|
|
const { commitBatchMetaUpdates } = this.props;
|
|
|
|
this.pushOnColumnEvent({
|
|
rowIndex,
|
|
action,
|
|
onComplete,
|
|
triggerPropertyName,
|
|
eventType,
|
|
row,
|
|
additionalData,
|
|
});
|
|
commitBatchMetaUpdates();
|
|
} else {
|
|
onComplete();
|
|
}
|
|
};
|
|
|
|
onDropdownOptionSelect = (action: string) => {
|
|
super.executeAction({
|
|
dynamicString: action,
|
|
event: {
|
|
type: EventType.ON_OPTION_CHANGE,
|
|
},
|
|
});
|
|
};
|
|
|
|
handleAllRowSelect = (pageData: Record<string, unknown>[]) => {
|
|
if (this.props.multiRowSelection) {
|
|
const selectedRowIndices = pageData.map(
|
|
(row: Record<string, unknown>) => row.index,
|
|
);
|
|
//single action no need to batch
|
|
this.props.updateWidgetMetaProperty(
|
|
"selectedRowIndices",
|
|
selectedRowIndices,
|
|
);
|
|
}
|
|
};
|
|
|
|
handleRowClick = (row: Record<string, unknown>, selectedIndex: number) => {
|
|
const { multiRowSelection, selectedRowIndex, selectedRowIndices } =
|
|
this.props;
|
|
// no need to batch actions here because it a time only one will execute
|
|
|
|
if (multiRowSelection) {
|
|
let indices: Array<number>;
|
|
|
|
if (_.isArray(selectedRowIndices)) {
|
|
indices = [...selectedRowIndices];
|
|
} else {
|
|
indices = [];
|
|
}
|
|
|
|
/*
|
|
* Deselect if the index is already present
|
|
*/
|
|
if (indices.includes(selectedIndex)) {
|
|
indices.splice(indices.indexOf(selectedIndex), 1);
|
|
this.props.updateWidgetMetaProperty("selectedRowIndices", indices);
|
|
} else {
|
|
/*
|
|
* select if the index is not present already
|
|
*/
|
|
indices.push(selectedIndex);
|
|
|
|
this.props.updateWidgetMetaProperty("selectedRowIndices", indices, {
|
|
triggerPropertyName: "onRowSelected",
|
|
dynamicString: this.props.onRowSelected,
|
|
event: {
|
|
type: EventType.ON_ROW_SELECTED,
|
|
},
|
|
});
|
|
}
|
|
} else {
|
|
let index;
|
|
|
|
if (isNumber(selectedRowIndex)) {
|
|
index = selectedRowIndex;
|
|
} else {
|
|
index = -1;
|
|
}
|
|
|
|
if (index !== selectedIndex) {
|
|
this.props.updateWidgetMetaProperty("selectedRowIndex", selectedIndex, {
|
|
triggerPropertyName: "onRowSelected",
|
|
dynamicString: this.props.onRowSelected,
|
|
event: {
|
|
type: EventType.ON_ROW_SELECTED,
|
|
},
|
|
});
|
|
} else {
|
|
this.props.updateWidgetMetaProperty("selectedRowIndex", -1);
|
|
}
|
|
}
|
|
};
|
|
|
|
updatePageNumber = (pageNo: number, event?: EventType) => {
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
const paginationDirection =
|
|
event == EventType.ON_NEXT_PAGE
|
|
? PaginationDirection.NEXT_PAGE
|
|
: PaginationDirection.PREVIOUS_PAGE;
|
|
this.updatePaginationDirectionFlags(paginationDirection);
|
|
|
|
if (event) {
|
|
pushBatchMetaUpdates("pageNo", pageNo, {
|
|
triggerPropertyName: "onPageChange",
|
|
dynamicString: this.props.onPageChange,
|
|
event: {
|
|
type: event,
|
|
},
|
|
});
|
|
} else {
|
|
pushBatchMetaUpdates("pageNo", pageNo);
|
|
}
|
|
|
|
if (this.props.onPageChange) {
|
|
this.pushResetSelectedRowIndexUpdates();
|
|
}
|
|
commitBatchMetaUpdates();
|
|
};
|
|
|
|
updatePaginationDirectionFlags = (direction?: PaginationDirection) => {
|
|
const { pushBatchMetaUpdates } = this.props;
|
|
|
|
let previousButtonFlag = false;
|
|
let nextButtonFlag = false;
|
|
|
|
if (direction) {
|
|
switch (direction) {
|
|
case PaginationDirection.INITIAL: {
|
|
previousButtonFlag = false;
|
|
nextButtonFlag = false;
|
|
break;
|
|
}
|
|
case PaginationDirection.NEXT_PAGE: {
|
|
nextButtonFlag = true;
|
|
break;
|
|
}
|
|
case PaginationDirection.PREVIOUS_PAGE: {
|
|
previousButtonFlag = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pushBatchMetaUpdates("previousPageVisited", previousButtonFlag);
|
|
pushBatchMetaUpdates("nextPageVisited", nextButtonFlag);
|
|
};
|
|
|
|
handleNextPageClick = () => {
|
|
const pageNo = (this.props.pageNo || 1) + 1;
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
this.updatePaginationDirectionFlags(PaginationDirection.NEXT_PAGE);
|
|
|
|
pushBatchMetaUpdates("pageNo", pageNo, {
|
|
triggerPropertyName: "onPageChange",
|
|
dynamicString: this.props.onPageChange,
|
|
event: {
|
|
type: EventType.ON_NEXT_PAGE,
|
|
},
|
|
});
|
|
|
|
if (this.props.onPageChange) {
|
|
this.pushResetSelectedRowIndexUpdates();
|
|
}
|
|
commitBatchMetaUpdates();
|
|
};
|
|
|
|
pushResetSelectedRowIndexUpdates = (skipDefault?: boolean) => {
|
|
const { pushBatchMetaUpdates } = this.props;
|
|
|
|
const {
|
|
defaultSelectedRowIndex,
|
|
defaultSelectedRowIndices,
|
|
multiRowSelection,
|
|
} = this.props;
|
|
|
|
if (multiRowSelection) {
|
|
pushBatchMetaUpdates(
|
|
"selectedRowIndices",
|
|
skipDefault ? [] : defaultSelectedRowIndices,
|
|
);
|
|
} else {
|
|
pushBatchMetaUpdates(
|
|
"selectedRowIndex",
|
|
skipDefault ? -1 : defaultSelectedRowIndex,
|
|
);
|
|
}
|
|
};
|
|
|
|
unSelectAllRow = () => {
|
|
this.props.updateWidgetMetaProperty("selectedRowIndices", []);
|
|
};
|
|
|
|
handlePrevPageClick = () => {
|
|
const pageNo = (this.props.pageNo || 1) - 1;
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
if (pageNo >= 1) {
|
|
this.updatePaginationDirectionFlags(PaginationDirection.PREVIOUS_PAGE);
|
|
pushBatchMetaUpdates("pageNo", pageNo, {
|
|
triggerPropertyName: "onPageChange",
|
|
dynamicString: this.props.onPageChange,
|
|
event: {
|
|
type: EventType.ON_PREV_PAGE,
|
|
},
|
|
});
|
|
|
|
if (this.props.onPageChange) {
|
|
this.pushResetSelectedRowIndexUpdates();
|
|
}
|
|
}
|
|
commitBatchMetaUpdates();
|
|
};
|
|
|
|
getColumnIdByAlias(alias: string) {
|
|
const { primaryColumns } = this.props;
|
|
|
|
if (primaryColumns) {
|
|
const column = Object.values(primaryColumns).find(
|
|
(column) => column.alias === alias,
|
|
);
|
|
|
|
if (column) {
|
|
return column.id;
|
|
}
|
|
}
|
|
|
|
return alias;
|
|
}
|
|
|
|
getColumnByOriginalId(originalId: string) {
|
|
return Object.values(this.props.primaryColumns).find((column) => {
|
|
return column.originalId === originalId;
|
|
});
|
|
}
|
|
|
|
pushTransientTableDataActionsUpdates = (data: TransientDataPayload) => {
|
|
const { __originalIndex__, ...transientData } = data;
|
|
const { pushBatchMetaUpdates } = this.props;
|
|
|
|
pushBatchMetaUpdates("transientTableData", {
|
|
...this.props.transientTableData,
|
|
[__originalIndex__]: {
|
|
...this.props.transientTableData[__originalIndex__],
|
|
...transientData,
|
|
},
|
|
});
|
|
|
|
pushBatchMetaUpdates("updatedRowIndex", __originalIndex__);
|
|
};
|
|
|
|
removeRowFromTransientTableData = (index: number) => {
|
|
const newTransientTableData = clone(this.props.transientTableData);
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
if (newTransientTableData) {
|
|
delete newTransientTableData[index];
|
|
|
|
pushBatchMetaUpdates("transientTableData", newTransientTableData);
|
|
}
|
|
pushBatchMetaUpdates("updatedRowIndex", -1);
|
|
commitBatchMetaUpdates();
|
|
};
|
|
|
|
getRowOriginalIndex = (index: number) => {
|
|
const { filteredTableData } = this.props;
|
|
|
|
if (filteredTableData) {
|
|
const row = filteredTableData[index];
|
|
|
|
if (row) {
|
|
return row[ORIGINAL_INDEX_KEY];
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
|
|
onBulkEditSave = () => {
|
|
this.props.updateWidgetMetaProperty(
|
|
"transientTableData",
|
|
this.props.transientTableData,
|
|
{
|
|
triggerPropertyName: "onBulkSave",
|
|
dynamicString: this.props.onBulkSave,
|
|
event: {
|
|
type: EventType.ON_BULK_SAVE,
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
onBulkEditDiscard = () => {
|
|
this.props.updateWidgetMetaProperty(
|
|
"transientTableData",
|
|
{},
|
|
{
|
|
triggerPropertyName: "onBulkDiscard",
|
|
dynamicString: this.props.onBulkDiscard,
|
|
event: {
|
|
type: EventType.ON_BULK_DISCARD,
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
renderCell = (props: any) => {
|
|
const column =
|
|
this.getColumnByOriginalId(
|
|
props.cell.column.columnProperties.originalId,
|
|
) || props.cell.column.columnProperties;
|
|
const rowIndex = props.cell.row.index;
|
|
|
|
/*
|
|
* We don't need to render cells that don't display data (button, iconButton, etc)
|
|
*/
|
|
if (
|
|
this.props.isAddRowInProgress &&
|
|
rowIndex === 0 &&
|
|
ActionColumnTypes.includes(column.columnType)
|
|
) {
|
|
return <CellWrapper />;
|
|
}
|
|
|
|
const isHidden = !column.isVisible;
|
|
const {
|
|
compactMode = CompactModeTypes.DEFAULT,
|
|
filteredTableData = [],
|
|
multiRowSelection,
|
|
selectedRowIndex,
|
|
selectedRowIndices,
|
|
} = this.props;
|
|
let row;
|
|
let originalIndex: number;
|
|
|
|
/*
|
|
* In add new row flow, a temporary row is injected at the top of the tableData, which doesn't
|
|
* have original row index value. so we are using -1 as the value
|
|
*/
|
|
if (this.props.isAddRowInProgress) {
|
|
row = filteredTableData[rowIndex - 1];
|
|
originalIndex = rowIndex === 0 ? -1 : row[ORIGINAL_INDEX_KEY] ?? rowIndex;
|
|
} else {
|
|
row = filteredTableData[rowIndex];
|
|
originalIndex = row[ORIGINAL_INDEX_KEY] ?? rowIndex;
|
|
}
|
|
|
|
const isNewRow = this.props.isAddRowInProgress && rowIndex === 0;
|
|
|
|
/*
|
|
* cellProperties order or size does not change when filter/sorting/grouping is applied
|
|
* on the data thus original index is needed to identify the column's cell property.
|
|
*/
|
|
const cellProperties = getCellProperties(column, originalIndex, isNewRow);
|
|
let isSelected = false;
|
|
|
|
if (this.props.transientTableData) {
|
|
cellProperties.hasUnsavedChanges =
|
|
this.props.transientTableData.hasOwnProperty(originalIndex) &&
|
|
this.props.transientTableData[originalIndex].hasOwnProperty(
|
|
props.cell.column.columnProperties.alias,
|
|
);
|
|
}
|
|
|
|
if (multiRowSelection) {
|
|
isSelected =
|
|
_.isArray(selectedRowIndices) && selectedRowIndices.includes(rowIndex);
|
|
} else {
|
|
isSelected = selectedRowIndex === rowIndex;
|
|
}
|
|
|
|
const isColumnEditable =
|
|
column.isEditable && isColumnTypeEditable(column.columnType);
|
|
const alias = props.cell.column.columnProperties.alias;
|
|
|
|
const isCellEditable = isColumnEditable && cellProperties.isCellEditable;
|
|
|
|
const isCellEditMode =
|
|
(props.cell.column.alias === this.props.editableCell?.column &&
|
|
rowIndex === this.props.editableCell?.index) ||
|
|
(isNewRow && isColumnEditable);
|
|
|
|
const shouldDisableEdit =
|
|
(this.props.inlineEditingSaveOption ===
|
|
InlineEditingSaveOptions.ROW_LEVEL &&
|
|
this.props.updatedRowIndices.length &&
|
|
this.props.updatedRowIndices.indexOf(originalIndex) === -1) ||
|
|
(this.hasInvalidColumnCell() && !isNewRow);
|
|
|
|
const disabledEditMessage = `Save or discard the ${
|
|
this.props.isAddRowInProgress ? "newly added" : "unsaved"
|
|
} row to start editing here`;
|
|
|
|
if (this.props.isAddRowInProgress) {
|
|
cellProperties.isCellDisabled = rowIndex !== 0;
|
|
|
|
if (rowIndex === 0) {
|
|
cellProperties.cellBackground = "";
|
|
}
|
|
}
|
|
|
|
switch (column.columnType) {
|
|
case ColumnTypes.BUTTON:
|
|
return (
|
|
<ButtonCell
|
|
allowCellWrapping={cellProperties.allowCellWrapping}
|
|
cellBackground={cellProperties.cellBackground}
|
|
columnActions={[
|
|
{
|
|
backgroundColor:
|
|
cellProperties.buttonColor || this.props.accentColor,
|
|
eventType: EventType.ON_CLICK,
|
|
id: column.id,
|
|
isVisible: true,
|
|
label: cellProperties.buttonLabel || DEFAULT_BUTTON_LABEL,
|
|
dynamicTrigger: column.onClick || "",
|
|
variant: cellProperties.buttonVariant,
|
|
borderRadius:
|
|
cellProperties.borderRadius || this.props.borderRadius,
|
|
boxShadow: cellProperties.boxShadow,
|
|
},
|
|
]}
|
|
compactMode={compactMode}
|
|
fontStyle={cellProperties.fontStyle}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellVisible={cellProperties.isCellVisible ?? true}
|
|
isDisabled={!!cellProperties.isDisabled}
|
|
isHidden={isHidden}
|
|
isSelected={isSelected}
|
|
onCommandClick={(action: string, onComplete: () => void) =>
|
|
this.onColumnEvent({
|
|
rowIndex,
|
|
action,
|
|
onComplete,
|
|
triggerPropertyName: "onClick",
|
|
eventType: EventType.ON_CLICK,
|
|
})
|
|
}
|
|
textColor={cellProperties.textColor}
|
|
textSize={cellProperties.textSize}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
/>
|
|
);
|
|
|
|
case ColumnTypes.EDIT_ACTIONS:
|
|
return (
|
|
<EditActionCell
|
|
allowCellWrapping={cellProperties.allowCellWrapping}
|
|
cellBackground={cellProperties.cellBackground}
|
|
columnActions={[
|
|
{
|
|
id: EditableCellActions.SAVE,
|
|
label: cellProperties.saveActionLabel,
|
|
dynamicTrigger: column.onSave || "",
|
|
eventType: EventType.ON_ROW_SAVE,
|
|
iconName: cellProperties.saveActionIconName,
|
|
variant: cellProperties.saveButtonVariant,
|
|
backgroundColor:
|
|
cellProperties.saveButtonColor || this.props.accentColor,
|
|
iconAlign: cellProperties.saveIconAlign,
|
|
borderRadius:
|
|
cellProperties.saveBorderRadius || this.props.borderRadius,
|
|
isVisible: cellProperties.isSaveVisible,
|
|
isDisabled:
|
|
cellProperties.isSaveDisabled || this.hasInvalidColumnCell(),
|
|
boxShadow: cellProperties.boxShadow,
|
|
},
|
|
{
|
|
id: EditableCellActions.DISCARD,
|
|
label: cellProperties.discardActionLabel,
|
|
dynamicTrigger: column.onDiscard || "",
|
|
eventType: EventType.ON_ROW_DISCARD,
|
|
iconName: cellProperties.discardActionIconName,
|
|
variant: cellProperties.discardButtonVariant,
|
|
backgroundColor:
|
|
cellProperties.discardButtonColor || this.props.accentColor,
|
|
iconAlign: cellProperties.discardIconAlign,
|
|
borderRadius:
|
|
cellProperties.discardBorderRadius || this.props.borderRadius,
|
|
isVisible: cellProperties.isDiscardVisible,
|
|
isDisabled:
|
|
cellProperties.isDiscardDisabled ||
|
|
this.hasInvalidColumnCell(),
|
|
boxShadow: cellProperties.boxShadow,
|
|
},
|
|
]}
|
|
compactMode={compactMode}
|
|
fontStyle={cellProperties.fontStyle}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellVisible={cellProperties.isCellVisible}
|
|
isHidden={isHidden}
|
|
isSelected={isSelected}
|
|
onCommandClick={(
|
|
action: string,
|
|
onComplete: () => void,
|
|
eventType: EventType,
|
|
) =>
|
|
this.onColumnEvent({
|
|
rowIndex,
|
|
action,
|
|
onComplete,
|
|
triggerPropertyName: "onClick",
|
|
eventType: eventType,
|
|
})
|
|
}
|
|
onDiscard={() =>
|
|
this.removeRowFromTransientTableData(originalIndex)
|
|
}
|
|
textColor={cellProperties.textColor}
|
|
textSize={cellProperties.textSize}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
/>
|
|
);
|
|
|
|
case ColumnTypes.SELECT:
|
|
return (
|
|
<SelectCell
|
|
accentColor={this.props.accentColor}
|
|
alias={props.cell.column.columnProperties.alias}
|
|
allowCellWrapping={cellProperties.allowCellWrapping}
|
|
autoOpen={!this.props.isAddRowInProgress}
|
|
borderRadius={cellProperties.borderRadius}
|
|
cellBackground={cellProperties.cellBackground}
|
|
columnType={column.columnType}
|
|
compactMode={compactMode}
|
|
disabledEditIcon={
|
|
shouldDisableEdit || this.props.isAddRowInProgress
|
|
}
|
|
disabledEditIconMessage={disabledEditMessage}
|
|
filterText={
|
|
this.props.selectColumnFilterText?.[
|
|
this.props.editableCell?.column || column.alias
|
|
]
|
|
}
|
|
fontStyle={cellProperties.fontStyle}
|
|
hasUnsavedChanges={cellProperties.hasUnsavedChanges}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellEditMode={isCellEditMode}
|
|
isCellEditable={isCellEditable}
|
|
isCellVisible={cellProperties.isCellVisible ?? true}
|
|
isEditable={isColumnEditable}
|
|
isFilterable={cellProperties.isFilterable}
|
|
isHidden={isHidden}
|
|
isNewRow={isNewRow}
|
|
key={props.key}
|
|
onFilterChange={this.onSelectFilterChange}
|
|
onFilterChangeActionString={column.onFilterUpdate}
|
|
onItemSelect={this.onOptionSelect}
|
|
onOptionSelectActionString={column.onOptionChange}
|
|
options={cellProperties.selectOptions}
|
|
placeholderText={cellProperties.placeholderText}
|
|
resetFilterTextOnClose={cellProperties.resetFilterTextOnClose}
|
|
rowIndex={rowIndex}
|
|
serverSideFiltering={cellProperties.serverSideFiltering}
|
|
tableWidth={this.props.componentWidth}
|
|
textColor={cellProperties.textColor}
|
|
textSize={cellProperties.textSize}
|
|
toggleCellEditMode={this.toggleCellEditMode}
|
|
value={props.cell.value}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
width={
|
|
this.props.columnWidthMap?.[column.id] || DEFAULT_COLUMN_WIDTH
|
|
}
|
|
/>
|
|
);
|
|
|
|
case ColumnTypes.IMAGE:
|
|
const onClick = column.onClick
|
|
? () =>
|
|
this.onColumnEvent({
|
|
rowIndex,
|
|
action: column.onClick,
|
|
triggerPropertyName: "onClick",
|
|
eventType: EventType.ON_CLICK,
|
|
})
|
|
: noop;
|
|
|
|
return (
|
|
<ImageCell
|
|
allowCellWrapping={cellProperties.allowCellWrapping}
|
|
cellBackground={cellProperties.cellBackground}
|
|
compactMode={compactMode}
|
|
fontStyle={cellProperties.fontStyle}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
imageSize={cellProperties.imageSize}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellVisible={cellProperties.isCellVisible ?? true}
|
|
isHidden={isHidden}
|
|
isSelected={isSelected}
|
|
onClick={onClick}
|
|
textColor={cellProperties.textColor}
|
|
textSize={cellProperties.textSize}
|
|
value={props.cell.value}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
/>
|
|
);
|
|
|
|
case ColumnTypes.MENU_BUTTON:
|
|
const getVisibleItems = (rowIndex: number) => {
|
|
const { configureMenuItems, menuItems, menuItemsSource, sourceData } =
|
|
cellProperties;
|
|
|
|
if (menuItemsSource === MenuItemsSource.STATIC && menuItems) {
|
|
const visibleItems = Object.values(menuItems)?.filter((item) =>
|
|
getBooleanPropertyValue(item.isVisible, rowIndex),
|
|
);
|
|
|
|
return visibleItems?.length
|
|
? orderBy(visibleItems, ["index"], ["asc"])
|
|
: [];
|
|
} else if (
|
|
menuItemsSource === MenuItemsSource.DYNAMIC &&
|
|
isArray(sourceData) &&
|
|
sourceData?.length &&
|
|
configureMenuItems?.config
|
|
) {
|
|
const { config } = configureMenuItems;
|
|
|
|
const getValue = (
|
|
propertyName: keyof MenuItem,
|
|
index: number,
|
|
rowIndex: number,
|
|
) => {
|
|
const value = config[propertyName];
|
|
|
|
if (isArray(value) && isArray(value[rowIndex])) {
|
|
return value[rowIndex][index];
|
|
} else if (isArray(value)) {
|
|
return value[index];
|
|
}
|
|
|
|
return value ?? null;
|
|
};
|
|
|
|
const visibleItems = sourceData
|
|
.map((item, index) => ({
|
|
...item,
|
|
id: index.toString(),
|
|
isVisible: getValue("isVisible", index, rowIndex),
|
|
isDisabled: getValue("isDisabled", index, rowIndex),
|
|
index: index,
|
|
widgetId: "",
|
|
label: getValue("label", index, rowIndex),
|
|
onClick: config?.onClick,
|
|
textColor: getValue("textColor", index, rowIndex),
|
|
backgroundColor: getValue("backgroundColor", index, rowIndex),
|
|
iconAlign: getValue("iconAlign", index, rowIndex),
|
|
iconColor: getValue("iconColor", index, rowIndex),
|
|
iconName: getValue("iconName", index, rowIndex),
|
|
}))
|
|
.filter((item) => item.isVisible === true);
|
|
|
|
return visibleItems;
|
|
}
|
|
|
|
return [];
|
|
};
|
|
|
|
return (
|
|
<MenuButtonCell
|
|
allowCellWrapping={cellProperties.allowCellWrapping}
|
|
borderRadius={
|
|
cellProperties.borderRadius || this.props.borderRadius
|
|
}
|
|
boxShadow={cellProperties.boxShadow}
|
|
cellBackground={cellProperties.cellBackground}
|
|
compactMode={compactMode}
|
|
configureMenuItems={cellProperties.configureMenuItems}
|
|
fontStyle={cellProperties.fontStyle}
|
|
getVisibleItems={getVisibleItems}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
iconAlign={cellProperties.iconAlign}
|
|
iconName={cellProperties.menuButtoniconName || undefined}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellVisible={cellProperties.isCellVisible ?? true}
|
|
isCompact={!!cellProperties.isCompact}
|
|
isDisabled={!!cellProperties.isDisabled}
|
|
isHidden={isHidden}
|
|
isSelected={isSelected}
|
|
label={cellProperties.menuButtonLabel ?? DEFAULT_MENU_BUTTON_LABEL}
|
|
menuColor={
|
|
cellProperties.menuColor || this.props.accentColor || Colors.GREEN
|
|
}
|
|
menuItems={cellProperties.menuItems}
|
|
menuItemsSource={cellProperties.menuItemsSource}
|
|
menuVariant={cellProperties.menuVariant ?? DEFAULT_MENU_VARIANT}
|
|
onCommandClick={(
|
|
action: string,
|
|
index?: number,
|
|
onComplete?: () => void,
|
|
) => {
|
|
const additionalData: Record<
|
|
string,
|
|
string | number | Record<string, unknown>
|
|
> = {};
|
|
|
|
if (cellProperties?.sourceData && _.isNumber(index)) {
|
|
additionalData.currentItem = cellProperties.sourceData[index];
|
|
additionalData.currentIndex = index;
|
|
}
|
|
|
|
return this.onColumnEvent({
|
|
rowIndex,
|
|
action,
|
|
onComplete,
|
|
triggerPropertyName: "onClick",
|
|
eventType: EventType.ON_CLICK,
|
|
additionalData,
|
|
});
|
|
}}
|
|
rowIndex={originalIndex}
|
|
sourceData={cellProperties.sourceData}
|
|
textColor={cellProperties.textColor}
|
|
textSize={cellProperties.textSize}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
/>
|
|
);
|
|
|
|
case ColumnTypes.ICON_BUTTON:
|
|
return (
|
|
<IconButtonCell
|
|
allowCellWrapping={cellProperties.allowCellWrapping}
|
|
borderRadius={
|
|
cellProperties.borderRadius || this.props.borderRadius
|
|
}
|
|
boxShadow={cellProperties.boxShadow || "NONE"}
|
|
buttonColor={
|
|
cellProperties.buttonColor ||
|
|
this.props.accentColor ||
|
|
Colors.GREEN
|
|
}
|
|
buttonVariant={cellProperties.buttonVariant || "PRIMARY"}
|
|
cellBackground={cellProperties.cellBackground}
|
|
columnActions={[
|
|
{
|
|
id: column.id,
|
|
dynamicTrigger: column.onClick || "",
|
|
},
|
|
]}
|
|
compactMode={compactMode}
|
|
disabled={!!cellProperties.isDisabled}
|
|
fontStyle={cellProperties.fontStyle}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
iconName={(cellProperties.iconName || IconNames.ADD) as IconName}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellVisible={cellProperties.isCellVisible ?? true}
|
|
isHidden={isHidden}
|
|
isSelected={isSelected}
|
|
onCommandClick={(action: string, onComplete: () => void) =>
|
|
this.onColumnEvent({
|
|
rowIndex,
|
|
action,
|
|
onComplete,
|
|
triggerPropertyName: "onClick",
|
|
eventType: EventType.ON_CLICK,
|
|
})
|
|
}
|
|
textColor={cellProperties.textColor}
|
|
textSize={cellProperties.textSize}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
/>
|
|
);
|
|
|
|
case ColumnTypes.VIDEO:
|
|
return (
|
|
<VideoCell
|
|
allowCellWrapping={cellProperties.allowCellWrapping}
|
|
cellBackground={cellProperties.cellBackground}
|
|
compactMode={compactMode}
|
|
fontStyle={cellProperties.fontStyle}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellVisible={cellProperties.isCellVisible ?? true}
|
|
isHidden={isHidden}
|
|
textColor={cellProperties.textColor}
|
|
textSize={cellProperties.textSize}
|
|
value={props.cell.value}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
/>
|
|
);
|
|
|
|
case ColumnTypes.CHECKBOX:
|
|
return (
|
|
<CheckboxCell
|
|
accentColor={this.props.accentColor}
|
|
borderRadius={
|
|
cellProperties.borderRadius || this.props.borderRadius
|
|
}
|
|
cellBackground={cellProperties.cellBackground}
|
|
compactMode={compactMode}
|
|
disabledCheckbox={
|
|
shouldDisableEdit || (this.props.isAddRowInProgress && !isNewRow)
|
|
}
|
|
disabledCheckboxMessage={disabledEditMessage}
|
|
hasUnSavedChanges={cellProperties.hasUnsavedChanges}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellEditable={isCellEditable}
|
|
isCellVisible={cellProperties.isCellVisible ?? true}
|
|
isHidden={isHidden}
|
|
onChange={() =>
|
|
this.onCheckChange(
|
|
column,
|
|
props.cell.row.values,
|
|
!props.cell.value,
|
|
alias,
|
|
originalIndex,
|
|
rowIndex,
|
|
)
|
|
}
|
|
value={props.cell.value}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
/>
|
|
);
|
|
|
|
case ColumnTypes.SWITCH:
|
|
return (
|
|
<SwitchCell
|
|
accentColor={this.props.accentColor}
|
|
cellBackground={cellProperties.cellBackground}
|
|
compactMode={compactMode}
|
|
disabledSwitch={
|
|
shouldDisableEdit || (this.props.isAddRowInProgress && !isNewRow)
|
|
}
|
|
disabledSwitchMessage={disabledEditMessage}
|
|
hasUnSavedChanges={cellProperties.hasUnsavedChanges}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellEditable={isCellEditable}
|
|
isCellVisible={cellProperties.isCellVisible ?? true}
|
|
isHidden={isHidden}
|
|
onChange={() =>
|
|
this.onCheckChange(
|
|
column,
|
|
props.cell.row.values,
|
|
!props.cell.value,
|
|
alias,
|
|
originalIndex,
|
|
rowIndex,
|
|
)
|
|
}
|
|
value={props.cell.value}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
/>
|
|
);
|
|
|
|
case ColumnTypes.DATE:
|
|
return (
|
|
<DateCell
|
|
accentColor={this.props.accentColor}
|
|
alias={props.cell.column.columnProperties.alias}
|
|
borderRadius={this.props.borderRadius}
|
|
cellBackground={cellProperties.cellBackground}
|
|
closeOnSelection
|
|
columnType={column.columnType}
|
|
compactMode={compactMode}
|
|
disabledEditIcon={
|
|
shouldDisableEdit || this.props.isAddRowInProgress
|
|
}
|
|
disabledEditIconMessage={disabledEditMessage}
|
|
firstDayOfWeek={props.cell.column.columnProperties.firstDayOfWeek}
|
|
fontStyle={cellProperties.fontStyle}
|
|
hasUnsavedChanges={cellProperties.hasUnsavedChanges}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
inputFormat={cellProperties.inputFormat}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellEditMode={isCellEditMode}
|
|
isCellEditable={isCellEditable}
|
|
isCellVisible={cellProperties.isCellVisible ?? true}
|
|
isEditableCellValid={this.isColumnCellValid(alias)}
|
|
isHidden={isHidden}
|
|
isNewRow={isNewRow}
|
|
isRequired={
|
|
props.cell.column.columnProperties.validation
|
|
.isColumnEditableCellRequired
|
|
}
|
|
maxDate={props.cell.column.columnProperties.validation.maxDate}
|
|
minDate={props.cell.column.columnProperties.validation.minDate}
|
|
onCellTextChange={this.onCellTextChange}
|
|
onDateSave={this.onDateSave}
|
|
onDateSelectedString={
|
|
props.cell.column.columnProperties.onDateSelected
|
|
}
|
|
outputFormat={cellProperties.outputFormat}
|
|
rowIndex={rowIndex}
|
|
shortcuts={cellProperties.shortcuts}
|
|
tableWidth={this.props.componentWidth}
|
|
textColor={cellProperties.textColor}
|
|
textSize={cellProperties.textSize}
|
|
timePrecision={cellProperties.timePrecision || TimePrecision.NONE}
|
|
toggleCellEditMode={this.toggleCellEditMode}
|
|
updateNewRowValues={this.updateNewRowValues}
|
|
validationErrorMessage="This field is required"
|
|
value={props.cell.value}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
widgetId={this.props.widgetId}
|
|
/>
|
|
);
|
|
|
|
default:
|
|
let validationErrorMessage;
|
|
|
|
if (isCellEditMode) {
|
|
validationErrorMessage =
|
|
column.validation.isColumnEditableCellRequired &&
|
|
(isNil(props.cell.value) || props.cell.value === "")
|
|
? "This field is required"
|
|
: column.validation?.errorMessage;
|
|
}
|
|
|
|
return (
|
|
<PlainTextCell
|
|
accentColor={this.props.accentColor}
|
|
alias={props.cell.column.columnProperties.alias}
|
|
allowCellWrapping={cellProperties.allowCellWrapping}
|
|
cellBackground={cellProperties.cellBackground}
|
|
columnType={column.columnType}
|
|
compactMode={compactMode}
|
|
currencyCode={cellProperties.currencyCode}
|
|
decimals={cellProperties.decimals}
|
|
disabledEditIcon={
|
|
shouldDisableEdit || this.props.isAddRowInProgress
|
|
}
|
|
disabledEditIconMessage={disabledEditMessage}
|
|
displayText={cellProperties.displayText}
|
|
fontStyle={cellProperties.fontStyle}
|
|
hasUnsavedChanges={cellProperties.hasUnsavedChanges}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellEditMode={isCellEditMode}
|
|
isCellEditable={isCellEditable}
|
|
isCellVisible={cellProperties.isCellVisible ?? true}
|
|
isEditableCellValid={this.isColumnCellValid(alias)}
|
|
isHidden={isHidden}
|
|
isNewRow={isNewRow}
|
|
notation={cellProperties.notation}
|
|
onCellTextChange={this.onCellTextChange}
|
|
onSubmitString={props.cell.column.columnProperties.onSubmit}
|
|
rowIndex={rowIndex}
|
|
tableWidth={this.props.componentWidth}
|
|
textColor={cellProperties.textColor}
|
|
textSize={cellProperties.textSize}
|
|
thousandSeparator={cellProperties.thousandSeparator}
|
|
toggleCellEditMode={this.toggleCellEditMode}
|
|
validationErrorMessage={validationErrorMessage}
|
|
value={props.cell.value}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
widgetId={this.props.widgetId}
|
|
/>
|
|
);
|
|
}
|
|
};
|
|
|
|
onCellTextChange = (
|
|
value: EditableCell["value"],
|
|
inputValue: string,
|
|
alias: string,
|
|
) => {
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
if (this.props.isAddRowInProgress) {
|
|
this.updateNewRowValues(alias, inputValue, value);
|
|
} else {
|
|
pushBatchMetaUpdates("editableCell", {
|
|
...this.props.editableCell,
|
|
value: value,
|
|
inputValue,
|
|
});
|
|
|
|
if (this.props.editableCell?.column) {
|
|
pushBatchMetaUpdates("columnEditableCellValue", {
|
|
...this.props.columnEditableCellValue,
|
|
[this.props.editableCell?.column]: value,
|
|
});
|
|
}
|
|
commitBatchMetaUpdates();
|
|
}
|
|
};
|
|
|
|
toggleCellEditMode = (
|
|
enable: boolean,
|
|
rowIndex: number,
|
|
alias: string,
|
|
value: string | number,
|
|
onSubmit?: string,
|
|
action?: EditableCellActions,
|
|
) => {
|
|
if (this.props.isAddRowInProgress) {
|
|
return;
|
|
}
|
|
|
|
if (enable) {
|
|
if (this.inlineEditTimer) {
|
|
clearTimeout(this.inlineEditTimer);
|
|
}
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
pushBatchMetaUpdates("editableCell", {
|
|
column: alias,
|
|
index: rowIndex,
|
|
value: value,
|
|
// To revert back to previous on discard
|
|
initialValue: value,
|
|
inputValue: value,
|
|
});
|
|
pushBatchMetaUpdates("columnEditableCellValue", {
|
|
...this.props.columnEditableCellValue,
|
|
[alias]: value,
|
|
});
|
|
|
|
/*
|
|
* We need to clear the selectedRowIndex and selectedRowIndices
|
|
* if the rows are sorted, to avoid selectedRow jumping to
|
|
* different page.
|
|
*/
|
|
if (this.props.sortOrder.column) {
|
|
if (this.props.multiRowSelection) {
|
|
pushBatchMetaUpdates("selectedRowIndices", []);
|
|
} else {
|
|
pushBatchMetaUpdates("selectedRowIndex", -1);
|
|
}
|
|
}
|
|
commitBatchMetaUpdates();
|
|
} else {
|
|
if (
|
|
this.isColumnCellValid(alias) &&
|
|
action === EditableCellActions.SAVE &&
|
|
value !== this.props.editableCell?.initialValue
|
|
) {
|
|
const { commitBatchMetaUpdates } = this.props;
|
|
|
|
this.pushTransientTableDataActionsUpdates({
|
|
[ORIGINAL_INDEX_KEY]: this.getRowOriginalIndex(rowIndex),
|
|
[alias]: this.props.editableCell?.value,
|
|
});
|
|
|
|
if (onSubmit && this.props.editableCell?.column) {
|
|
//since onSubmit is truthy that makes action truthy as well, so we can push this event
|
|
this.pushOnColumnEvent({
|
|
rowIndex: rowIndex,
|
|
action: onSubmit,
|
|
triggerPropertyName: "onSubmit",
|
|
eventType: EventType.ON_SUBMIT,
|
|
row: {
|
|
...this.props.filteredTableData[rowIndex],
|
|
[this.props.editableCell.column]: this.props.editableCell.value,
|
|
},
|
|
});
|
|
}
|
|
commitBatchMetaUpdates();
|
|
|
|
this.clearEditableCell();
|
|
} else if (
|
|
action === EditableCellActions.DISCARD ||
|
|
value === this.props.editableCell?.initialValue
|
|
) {
|
|
this.clearEditableCell();
|
|
}
|
|
}
|
|
};
|
|
|
|
onDateSave = (
|
|
rowIndex: number,
|
|
alias: string,
|
|
value: string,
|
|
onSubmit?: string,
|
|
) => {
|
|
if (this.isColumnCellValid(alias)) {
|
|
const { commitBatchMetaUpdates } = this.props;
|
|
|
|
this.pushTransientTableDataActionsUpdates({
|
|
[ORIGINAL_INDEX_KEY]: this.getRowOriginalIndex(rowIndex),
|
|
[alias]: value,
|
|
});
|
|
|
|
if (onSubmit && this.props.editableCell?.column) {
|
|
//since onSubmit is truthy this makes action truthy as well, so we can push this event
|
|
this.pushOnColumnEvent({
|
|
rowIndex: rowIndex,
|
|
action: onSubmit,
|
|
triggerPropertyName: "onSubmit",
|
|
eventType: EventType.ON_SUBMIT,
|
|
row: {
|
|
...this.props.filteredTableData[rowIndex],
|
|
[this.props.editableCell.column]: value,
|
|
},
|
|
});
|
|
}
|
|
|
|
commitBatchMetaUpdates();
|
|
this.clearEditableCell();
|
|
}
|
|
};
|
|
pushClearEditableCellsUpdates = () => {
|
|
const { pushBatchMetaUpdates } = this.props;
|
|
|
|
pushBatchMetaUpdates("editableCell", defaultEditableCell);
|
|
pushBatchMetaUpdates("columnEditableCellValue", {});
|
|
};
|
|
|
|
clearEditableCell = (skipTimeout?: boolean) => {
|
|
const clear = () => {
|
|
const { commitBatchMetaUpdates } = this.props;
|
|
|
|
this.pushClearEditableCellsUpdates();
|
|
commitBatchMetaUpdates();
|
|
};
|
|
|
|
if (skipTimeout) {
|
|
clear();
|
|
} else {
|
|
/*
|
|
* We need to let the evaulations compute derived property (filteredTableData)
|
|
* before we clear the editableCell to avoid the text flickering
|
|
*/
|
|
this.inlineEditTimer = setTimeout(clear, 100);
|
|
}
|
|
};
|
|
|
|
isColumnCellEditable = (column: ColumnProperties, rowIndex: number) => {
|
|
return (
|
|
column.alias === this.props.editableCell?.column &&
|
|
rowIndex === this.props.editableCell?.index
|
|
);
|
|
};
|
|
|
|
onOptionSelect = (
|
|
value: string | number,
|
|
rowIndex: number,
|
|
column: string,
|
|
action?: string,
|
|
) => {
|
|
if (this.props.isAddRowInProgress) {
|
|
this.updateNewRowValues(column, value, value);
|
|
} else {
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
this.pushTransientTableDataActionsUpdates({
|
|
[ORIGINAL_INDEX_KEY]: this.getRowOriginalIndex(rowIndex),
|
|
[column]: value,
|
|
});
|
|
pushBatchMetaUpdates("editableCell", defaultEditableCell);
|
|
|
|
if (action && this.props.editableCell?.column) {
|
|
//since action is truthy we can push this event
|
|
this.pushOnColumnEvent({
|
|
rowIndex,
|
|
action,
|
|
triggerPropertyName: "onOptionChange",
|
|
eventType: EventType.ON_OPTION_CHANGE,
|
|
row: {
|
|
...this.props.filteredTableData[rowIndex],
|
|
[this.props.editableCell.column]: value,
|
|
},
|
|
});
|
|
}
|
|
commitBatchMetaUpdates();
|
|
}
|
|
};
|
|
|
|
onSelectFilterChange = (
|
|
text: string,
|
|
rowIndex: number,
|
|
serverSideFiltering: boolean,
|
|
alias: string,
|
|
action?: string,
|
|
) => {
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
pushBatchMetaUpdates("selectColumnFilterText", {
|
|
...this.props.selectColumnFilterText,
|
|
[alias]: text,
|
|
});
|
|
|
|
if (action && serverSideFiltering) {
|
|
//since action is truthy we can push this event
|
|
this.pushOnColumnEvent({
|
|
rowIndex,
|
|
action,
|
|
triggerPropertyName: "onFilterUpdate",
|
|
eventType: EventType.ON_FILTER_UPDATE,
|
|
row: {
|
|
...this.props.filteredTableData[rowIndex],
|
|
},
|
|
additionalData: {
|
|
filterText: text,
|
|
},
|
|
});
|
|
}
|
|
commitBatchMetaUpdates();
|
|
};
|
|
|
|
onCheckChange = (
|
|
column: any,
|
|
row: Record<string, unknown>,
|
|
value: boolean,
|
|
alias: string,
|
|
originalIndex: number,
|
|
rowIndex: number,
|
|
) => {
|
|
if (this.props.isAddRowInProgress) {
|
|
this.updateNewRowValues(alias, value, value);
|
|
} else {
|
|
const { commitBatchMetaUpdates } = this.props;
|
|
|
|
this.pushTransientTableDataActionsUpdates({
|
|
[ORIGINAL_INDEX_KEY]: originalIndex,
|
|
[alias]: value,
|
|
});
|
|
commitBatchMetaUpdates();
|
|
//cannot batch this update because we are not sure if it action is truthy or not
|
|
this.onColumnEvent({
|
|
rowIndex,
|
|
action: column.onCheckChange,
|
|
triggerPropertyName: "onCheckChange",
|
|
eventType: EventType.ON_CHECK_CHANGE,
|
|
row: {
|
|
...row,
|
|
[alias]: value,
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
handleAddNewRowClick = () => {
|
|
const defaultNewRow = this.props.defaultNewRow || {};
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
pushBatchMetaUpdates("isAddRowInProgress", true);
|
|
pushBatchMetaUpdates("newRowContent", defaultNewRow);
|
|
pushBatchMetaUpdates("newRow", defaultNewRow);
|
|
|
|
// New row gets added at the top of page 1 when client side pagination enabled
|
|
if (!this.props.serverSidePaginationEnabled) {
|
|
this.updatePaginationDirectionFlags(PaginationDirection.INITIAL);
|
|
}
|
|
|
|
//Since we're adding a newRowContent thats not part of tableData, the index changes
|
|
// so we're resetting the row selection
|
|
pushBatchMetaUpdates("selectedRowIndex", -1);
|
|
pushBatchMetaUpdates("selectedRowIndices", []);
|
|
commitBatchMetaUpdates();
|
|
};
|
|
|
|
handleAddNewRowAction = (
|
|
type: AddNewRowActions,
|
|
onActionComplete: () => void,
|
|
) => {
|
|
let triggerPropertyName, action, eventType;
|
|
|
|
const onComplete = () => {
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
pushBatchMetaUpdates("isAddRowInProgress", false);
|
|
pushBatchMetaUpdates("newRowContent", undefined);
|
|
pushBatchMetaUpdates("newRow", undefined);
|
|
commitBatchMetaUpdates();
|
|
|
|
onActionComplete();
|
|
};
|
|
|
|
if (type === AddNewRowActions.SAVE) {
|
|
triggerPropertyName = "onAddNewRowSave";
|
|
action = this.props.onAddNewRowSave;
|
|
eventType = EventType.ON_ADD_NEW_ROW_SAVE;
|
|
} else {
|
|
triggerPropertyName = "onAddNewRowDiscard";
|
|
action = this.props.onAddNewRowDiscard;
|
|
eventType = EventType.ON_ADD_NEW_ROW_DISCARD;
|
|
}
|
|
|
|
if (action) {
|
|
super.executeAction({
|
|
triggerPropertyName: triggerPropertyName,
|
|
dynamicString: action,
|
|
event: {
|
|
type: eventType,
|
|
callback: onComplete,
|
|
},
|
|
});
|
|
} else {
|
|
onComplete();
|
|
}
|
|
};
|
|
|
|
isColumnCellValid = (columnsAlias: string) => {
|
|
if (this.props.isEditableCellsValid?.hasOwnProperty(columnsAlias)) {
|
|
return this.props.isEditableCellsValid[columnsAlias];
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
hasInvalidColumnCell = () => {
|
|
if (isObject(this.props.isEditableCellsValid)) {
|
|
return Object.values(this.props.isEditableCellsValid).some((d) => !d);
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
updateNewRowValues = (
|
|
alias: string,
|
|
value: unknown,
|
|
parsedValue: unknown,
|
|
) => {
|
|
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
|
|
|
/*
|
|
* newRowContent holds whatever the user types while newRow holds the parsed value
|
|
* newRowContent is being used to populate the cell while newRow is being used
|
|
* for validations.
|
|
*/
|
|
pushBatchMetaUpdates("newRowContent", {
|
|
...this.props.newRowContent,
|
|
[alias]: value,
|
|
});
|
|
pushBatchMetaUpdates("newRow", {
|
|
...this.props.newRow,
|
|
[alias]: parsedValue,
|
|
});
|
|
commitBatchMetaUpdates();
|
|
};
|
|
|
|
onConnectData = () => {
|
|
if (this.props.renderMode === RenderModes.CANVAS) {
|
|
super.updateOneClickBindingOptionsVisibility(true);
|
|
}
|
|
};
|
|
}
|
|
|
|
export default TableWidgetV2;
|