## 🐞 Problem We've identified several issues with the TableWidgetV2's infinite scroll functionality: - **Stale Data After Toggle** When users enable infinite scroll for the first time, the table's state and cached data aren't properly reset, potentially leading to incorrect data display and inconsistent behavior. - **Height Changes Breaking Existing Data** When a table's height is increased while infinite scroll is enabled, the existing cached data becomes invalid due to changing offsets, but the table wasn't resetting its state accordingly. - **Empty Initial View** When loading a table with infinite scroll enabled, rows were not visible during the initial load until data was fetched, creating a jarring user experience. - **Disabled Properties Still Rendering Controls** Property controls that should be disabled (based on section disabling conditions) were still being rendered and active, causing unexpected behavior. --- ## ✅ Solution ### 1. Implement Table Reset on Infinite Scroll Toggle Added a new method `resetTableForInfiniteScroll()` that properly resets the table's state when infinite scroll is enabled. This method: - Clears cached table data - Resets the "end of data" flag - Resets all meta properties to their default values - Sets the page number back to `1` and triggers a page load ```ts resetTableForInfiniteScroll = () => { const { infiniteScrollEnabled, pushBatchMetaUpdates } = this.props; if (infiniteScrollEnabled) { // reset the cachedRows pushBatchMetaUpdates("cachedTableData", {}); pushBatchMetaUpdates("endOfData", false); // reset the meta properties const metaProperties = Object.keys(TableWidgetV2.getMetaPropertiesMap()); metaProperties.forEach((prop) => { if (prop !== "pageNo") { const defaultValue = TableWidgetV2.getMetaPropertiesMap()[prop]; this.props.updateWidgetMetaProperty(prop, defaultValue); } }); // reset and reload page this.updatePageNumber(1, EventType.ON_NEXT_PAGE); } }; ``` --- ### 2. Reset on Height Changes Added a check in `componentDidUpdate` to detect height changes and reset the table when needed: ```ts // Reset widget state when height changes while infinite scroll is enabled if ( infiniteScrollEnabled && prevProps.componentHeight !== componentHeight ) { this.resetTableForInfiniteScroll(); } ``` --- ### 3. Improved Empty State Rendering Modified the `InfiniteScrollBodyComponent` to show placeholder rows during initial load by using the maximum of `rows.length` and `pageSize`: ```ts const itemCount = useMemo( () => Math.max(rows.length, pageSize), [rows.length, pageSize], ); ``` This ensures the table maintains its expected height and appearance even before data is loaded. --- ### 4. Fixed Property Control Rendering Fixed the `PropertyControl` component to respect the `isControlDisabled` flag by conditionally rendering the control: ```ts {!isControlDisabled && PropertyControlFactory.createControl( config, { onPropertyChange: onPropertyChange, // ...other props }, // ...other args )} ``` This prevents disabled controls from being rendered and potentially causing issues. --- These improvements significantly enhance the stability and user experience of **TableWidgetV2**'s infinite scroll functionality. Fixes #39377 ## Automation /ok-to-test tags="@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity, @tag.PropertyPane" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/14373134089> > Commit: 2b0715bbbe2e9a254cd287f831329be529a17c3c > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14373134089&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity, @tag.PropertyPane` > Spec: > <hr>Thu, 10 Apr 2025 07:15:53 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Property panels now display controls only when enabled, enhancing clarity. - Table widgets offer smoother infinite scrolling with automatic resets on state or size changes. - Columns dynamically adjust for optimal display when infinite scrolling is active. - **Bug Fixes** - Improved handling of item counts and loading states in infinite scrolling. - **Refactor** - Improved performance through optimized item computations and streamlined scrolling logic. - Removed redundant loading button logic for a cleaner user experience. - **Tests** - Expanded test scenarios to verify improved content wrapping and rich HTML rendering in table cells, with a focus on internal logic and behavior. - Enhanced clarity and robustness of infinite scroll tests by verifying loading through scrolling actions. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Rahul Barwal <rahul.barwal@appsmith.com>
3111 lines
99 KiB
TypeScript
3111 lines
99 KiB
TypeScript
import log from "loglevel";
|
|
import memoizeOne from "memoize-one";
|
|
import React, { lazy, Suspense } from "react";
|
|
|
|
import _, {
|
|
filter,
|
|
isArray,
|
|
isEmpty,
|
|
isNil,
|
|
isNumber,
|
|
isObject,
|
|
isString,
|
|
merge,
|
|
orderBy,
|
|
pickBy,
|
|
union,
|
|
without,
|
|
xor,
|
|
xorWith,
|
|
} from "lodash";
|
|
|
|
import type { IconName } from "@blueprintjs/icons";
|
|
import { IconNames } from "@blueprintjs/icons";
|
|
import type { BatchPropertyUpdatePayload } from "actions/controlActions";
|
|
import Skeleton from "components/utils/Skeleton";
|
|
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
|
import { Colors } from "constants/Colors";
|
|
import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants";
|
|
import {
|
|
RenderModes,
|
|
WIDGET_PADDING,
|
|
WIDGET_TAGS,
|
|
} from "constants/WidgetConstants";
|
|
import type { SetterConfig, Stylesheet } from "entities/AppTheming";
|
|
import equal from "fast-deep-equal/es6";
|
|
import {
|
|
FlexVerticalAlignment,
|
|
ResponsiveBehavior,
|
|
} from "layoutSystems/common/utils/constants";
|
|
import { noop, retryPromise } from "utils/AppsmithUtils";
|
|
import type { ExtraDef } from "utils/autocomplete/defCreatorUtils";
|
|
import { generateTypeDef } from "utils/autocomplete/defCreatorUtils";
|
|
import type { DynamicPath } from "utils/DynamicBindingUtils";
|
|
import { klonaRegularWithTelemetry } from "utils/helpers";
|
|
import localStorage from "utils/localStorage";
|
|
import type {
|
|
AnvilConfig,
|
|
AutocompletionDefinitions,
|
|
PropertyUpdates,
|
|
SnipingModeProperty,
|
|
} from "WidgetProvider/constants";
|
|
import type {
|
|
WidgetQueryConfig,
|
|
WidgetQueryGenerationFormConfig,
|
|
} from "WidgetQueryGenerators/types";
|
|
import type { WidgetProps, WidgetState } from "widgets/BaseWidget";
|
|
import BaseWidget from "widgets/BaseWidget";
|
|
import { TimePrecision } from "widgets/DatePickerWidget2/constants";
|
|
import type { MenuItem } from "widgets/MenuButtonWidget/constants";
|
|
import { MenuItemsSource } from "widgets/MenuButtonWidget/constants";
|
|
import {
|
|
DefaultAutocompleteDefinitions,
|
|
sanitizeKey,
|
|
} from "widgets/WidgetUtils";
|
|
import { ButtonCell } from "../component/cellComponents/ButtonCell";
|
|
import { CheckboxCell } from "../component/cellComponents/CheckboxCell";
|
|
import { DateCell } from "../component/cellComponents/DateCell";
|
|
import { EditActionCell } from "../component/cellComponents/EditActionsCell";
|
|
import HTMLCell from "../component/cellComponents/HTMLCell";
|
|
import { IconButtonCell } from "../component/cellComponents/IconButtonCell";
|
|
import { ImageCell } from "../component/cellComponents/ImageCell";
|
|
import { MenuButtonCell } from "../component/cellComponents/MenuButtonCell";
|
|
import PlainTextCell from "../component/cellComponents/PlainTextCell";
|
|
import { SelectCell } from "../component/cellComponents/SelectCell";
|
|
import { SwitchCell } from "../component/cellComponents/SwitchCell";
|
|
import { VideoCell } from "../component/cellComponents/VideoCell";
|
|
import type {
|
|
ColumnProperties,
|
|
ReactTableColumnProps,
|
|
ReactTableFilter,
|
|
} from "../component/Constants";
|
|
import {
|
|
AddNewRowActions,
|
|
CompactModeTypes,
|
|
DEFAULT_FILTER,
|
|
SORT_ORDER,
|
|
SortOrderTypes,
|
|
StickyType,
|
|
} from "../component/Constants";
|
|
import { CellWrapper } from "../component/TableStyledWrappers";
|
|
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 IconSVG from "../icon.svg";
|
|
import ThumbnailSVG from "../thumbnail.svg";
|
|
import derivedProperties from "./parseDerivedProperties";
|
|
import contentConfig from "./propertyConfig/contentConfig";
|
|
import styleConfig from "./propertyConfig/styleConfig";
|
|
import type { getColumns } from "./reactTableUtils/getColumnsPureFn";
|
|
import { getMemoiseGetColumnsWithLocalStorageFn } from "./reactTableUtils/getColumnsPureFn";
|
|
import type {
|
|
tableData,
|
|
transformDataWithEditableCell,
|
|
} from "./reactTableUtils/transformDataPureFn";
|
|
import { getMemoiseTransformDataWithEditableCell } from "./reactTableUtils/transformDataPureFn";
|
|
import {
|
|
createEditActionColumn,
|
|
deleteLocalTableColumnOrderByWidgetId,
|
|
generateLocalNewColumnOrderFromStickyValue,
|
|
generateNewColumnOrderFromStickyValue,
|
|
getAllStickyColumnsCount,
|
|
getAllTableColumnKeys,
|
|
getBooleanPropertyValue,
|
|
getCellProperties,
|
|
getColumnOrderByWidgetIdFromLS,
|
|
getColumnType,
|
|
getDefaultColumnProperties,
|
|
getDerivedColumns,
|
|
getSelectRowIndex,
|
|
getSelectRowIndices,
|
|
getTableStyles,
|
|
isColumnTypeEditable,
|
|
updateAndSyncTableLocalColumnOrders,
|
|
} from "./utilities";
|
|
|
|
const ReactTableComponent = lazy(async () =>
|
|
retryPromise(async () => import("../component")),
|
|
);
|
|
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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;
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
memoiseGetColumnsWithLocalStorage: (localStorage: any) => getColumns;
|
|
memoiseTransformDataWithEditableCell: transformDataWithEditableCell;
|
|
|
|
static type = "TABLE_WIDGET_V2";
|
|
|
|
static getConfig() {
|
|
return {
|
|
name: "Table",
|
|
iconSVG: IconSVG,
|
|
thumbnailSVG: ThumbnailSVG,
|
|
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: false,
|
|
isVisibleDownload: true,
|
|
isVisiblePagination: true,
|
|
isSortable: true,
|
|
delimiter: ",",
|
|
version: 2,
|
|
inlineEditingSaveOption: InlineEditingSaveOptions.ROW_LEVEL,
|
|
enableServerSideFiltering: TableWidgetV2.getFeatureFlag(
|
|
ALLOW_TABLE_WIDGET_SERVER_SIDE_FILTERING,
|
|
)
|
|
? false
|
|
: undefined,
|
|
customIsLoading: false,
|
|
customIsLoadingValue: "",
|
|
cachedTableData: {},
|
|
endOfData: false,
|
|
};
|
|
}
|
|
|
|
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,
|
|
});
|
|
|
|
dynamicPropertyPathList.push({ key: "tableData" });
|
|
}
|
|
|
|
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 {
|
|
isLargeWidget: false,
|
|
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();
|
|
}
|
|
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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: "array",
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
/*
|
|
* Function to get the table columns with appropriate render functions
|
|
* based on columnType
|
|
*/
|
|
getTableColumns = () => {
|
|
const {
|
|
columnWidthMap,
|
|
isPreviewMode,
|
|
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,
|
|
isPreviewMode,
|
|
);
|
|
};
|
|
|
|
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();
|
|
}
|
|
|
|
// Commit Batch Updates property `true` is passed as commitBatchMetaUpdates is not called on componentDidMount and we need to call it for updating the batch updates
|
|
this.updateInfiniteScrollProperties(true);
|
|
}
|
|
|
|
componentDidUpdate(prevProps: TableWidgetProps) {
|
|
const {
|
|
commitBatchMetaUpdates,
|
|
componentHeight,
|
|
defaultSelectedRowIndex,
|
|
defaultSelectedRowIndices,
|
|
infiniteScrollEnabled,
|
|
pageNo,
|
|
pageSize,
|
|
primaryColumns = {},
|
|
pushBatchMetaUpdates,
|
|
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 = this.props.tableData !== prevProps.tableData;
|
|
|
|
// 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
|
|
*/
|
|
pushBatchMetaUpdates("transientTableData", {});
|
|
// reset updatedRowIndex whenever transientTableData is flushed.
|
|
pushBatchMetaUpdates("updatedRowIndex", -1);
|
|
|
|
/*
|
|
* Updating the caching layer on table data modification
|
|
* Commit Batch Updates property `false` is passed as commitBatchMetaUpdates is called on componentDidUpdate
|
|
* and we need not to explicitly call it for updating the batch updates
|
|
* */
|
|
this.updateInfiniteScrollProperties();
|
|
|
|
this.pushClearEditableCellsUpdates();
|
|
pushBatchMetaUpdates("selectColumnFilterText", {});
|
|
} else {
|
|
// TODO: reset the widget on any property change, like if the toggle of infinite scroll is enabled and previously it was disabled, currently we update cachedTableData property to the current tableData at pageNo.
|
|
/*
|
|
* Commit Batch Updates property `false` is passed as commitBatchMetaUpdates is called on componentDidUpdate
|
|
* and we need not to explicitly call it for updating the batch updates
|
|
* */
|
|
if (
|
|
!prevProps.infiniteScrollEnabled &&
|
|
this.props.infiniteScrollEnabled
|
|
) {
|
|
this.updateInfiniteScrollProperties();
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset widget state when infinite scroll is initially enabled
|
|
// This should come after all updateInfiniteScrollProperties are done
|
|
if (!prevProps.infiniteScrollEnabled && infiniteScrollEnabled) {
|
|
this.resetTableForInfiniteScroll();
|
|
}
|
|
|
|
// Reset widget state when height changes while infinite scroll is enabled
|
|
if (
|
|
infiniteScrollEnabled &&
|
|
prevProps.componentHeight !== componentHeight
|
|
) {
|
|
this.resetTableForInfiniteScroll();
|
|
}
|
|
|
|
/*
|
|
* 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 {
|
|
customIsLoading,
|
|
customIsLoadingValue,
|
|
customSortFunction: customSortFunctionData,
|
|
delimiter,
|
|
filteredTableData = [],
|
|
isVisibleDownload,
|
|
isVisibleFilters,
|
|
isVisiblePagination,
|
|
isVisibleSearch,
|
|
pageSize,
|
|
primaryColumns,
|
|
totalRecordsCount,
|
|
} = this.props;
|
|
|
|
const tableColumns = this.getTableColumns() || emptyArr;
|
|
let data = filteredTableData;
|
|
|
|
if (customSortFunctionData && Array.isArray(customSortFunctionData)) {
|
|
data = customSortFunctionData;
|
|
}
|
|
|
|
const transformedData = this.transformData(data, 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}
|
|
endOfData={this.props.endOfData}
|
|
filters={this.props.filters}
|
|
handleColumnFreeze={this.handleColumnFreeze}
|
|
handleReorderColumn={this.handleReorderColumn}
|
|
handleResizeColumn={this.handleResizeColumn}
|
|
height={componentHeight}
|
|
isAddRowInProgress={this.props.isAddRowInProgress}
|
|
isEditableCellsValid={this.props.isEditableCellsValid}
|
|
isInfiniteScrollEnabled={this.props.infiniteScrollEnabled}
|
|
isLoading={
|
|
customIsLoading
|
|
? customIsLoadingValue || this.props.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);
|
|
}
|
|
};
|
|
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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 = klonaRegularWithTelemetry(
|
|
this.props.transientTableData,
|
|
"TableWidgetV2.removeRowFromTransientTableData",
|
|
);
|
|
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,
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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 ? row[ORIGINAL_INDEX_KEY] ?? rowIndex : 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}
|
|
isEditableCellValid={this.isColumnCellValid(alias)}
|
|
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}
|
|
/>
|
|
);
|
|
|
|
case ColumnTypes.HTML:
|
|
return (
|
|
<HTMLCell
|
|
allowCellWrapping={cellProperties.allowCellWrapping}
|
|
cellBackground={cellProperties.cellBackground}
|
|
compactMode={compactMode}
|
|
fontStyle={cellProperties.fontStyle}
|
|
horizontalAlignment={cellProperties.horizontalAlignment}
|
|
isCellDisabled={cellProperties.isCellDisabled}
|
|
isCellVisible={cellProperties.isCellVisible ?? true}
|
|
isHidden={isHidden}
|
|
renderMode={this.props.renderMode}
|
|
textColor={cellProperties.textColor}
|
|
textSize={cellProperties.textSize}
|
|
value={props.cell.value}
|
|
verticalAlignment={cellProperties.verticalAlignment}
|
|
/>
|
|
);
|
|
|
|
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,
|
|
__originalIndex__: this.getRowOriginalIndex(rowIndex),
|
|
});
|
|
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,
|
|
) => {
|
|
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 = (
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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);
|
|
}
|
|
};
|
|
|
|
updateInfiniteScrollProperties(shouldCommitBatchUpdates?: boolean) {
|
|
const {
|
|
cachedTableData,
|
|
commitBatchMetaUpdates,
|
|
infiniteScrollEnabled,
|
|
pageNo,
|
|
pageSize,
|
|
processedTableData,
|
|
pushBatchMetaUpdates,
|
|
tableData,
|
|
totalRecordsCount,
|
|
} = this.props;
|
|
|
|
if (infiniteScrollEnabled) {
|
|
// Update the cache key for a particular page whenever this function is called. The pageNo data is updated with the tableData.
|
|
const updatedCachedTableData = {
|
|
...(cachedTableData || {}),
|
|
[pageNo]: tableData,
|
|
};
|
|
|
|
pushBatchMetaUpdates("cachedTableData", updatedCachedTableData);
|
|
|
|
// The check (!!totalRecordsCount && processedTableData.length === totalRecordsCount) is added if the totalRecordsCount property is set then match the length with the processedTableData which has all flatted data from each page in a single array except the current tableData page i.e. [ ...array of page 1 data, ...array of page 2 data ]. Another 'or' check is if (tableData.length < pageSize) when totalRecordsCount is undefined. Table data has a single page data and if the data comes out to be lesser than the pageSize, it is assumed that the data is finished.
|
|
if (
|
|
(!!totalRecordsCount &&
|
|
processedTableData.length + tableData.length === totalRecordsCount) ||
|
|
(!totalRecordsCount && tableData.length < pageSize)
|
|
) {
|
|
pushBatchMetaUpdates("endOfData", true);
|
|
} else {
|
|
pushBatchMetaUpdates("endOfData", false);
|
|
}
|
|
|
|
if (shouldCommitBatchUpdates) {
|
|
commitBatchMetaUpdates();
|
|
}
|
|
}
|
|
}
|
|
|
|
resetTableForInfiniteScroll = () => {
|
|
const {
|
|
infiniteScrollEnabled,
|
|
pushBatchMetaUpdates,
|
|
updateWidgetMetaProperty,
|
|
} = this.props;
|
|
|
|
if (infiniteScrollEnabled) {
|
|
// reset the cachedRows
|
|
const isAlreadyOnFirstPage = this.props.pageNo === 1;
|
|
const data = isAlreadyOnFirstPage ? { 1: this.props.tableData } : {};
|
|
|
|
pushBatchMetaUpdates("cachedTableData", data);
|
|
pushBatchMetaUpdates("endOfData", false);
|
|
|
|
// Explicitly reset specific meta properties
|
|
updateWidgetMetaProperty("selectedRowIndex", undefined);
|
|
updateWidgetMetaProperty("selectedRowIndices", undefined);
|
|
updateWidgetMetaProperty("searchText", undefined);
|
|
updateWidgetMetaProperty("triggeredRowIndex", undefined);
|
|
updateWidgetMetaProperty("filters", []);
|
|
updateWidgetMetaProperty("sortOrder", {
|
|
column: "",
|
|
order: null,
|
|
});
|
|
updateWidgetMetaProperty("transientTableData", {});
|
|
updateWidgetMetaProperty("updatedRowIndex", -1);
|
|
updateWidgetMetaProperty("editableCell", defaultEditableCell);
|
|
updateWidgetMetaProperty("columnEditableCellValue", {});
|
|
updateWidgetMetaProperty("selectColumnFilterText", {});
|
|
updateWidgetMetaProperty("isAddRowInProgress", false);
|
|
updateWidgetMetaProperty("newRowContent", undefined);
|
|
updateWidgetMetaProperty("newRow", undefined);
|
|
updateWidgetMetaProperty("previousPageVisited", false);
|
|
updateWidgetMetaProperty("nextPageVisited", false);
|
|
|
|
// reset and reload page
|
|
if (!isAlreadyOnFirstPage) {
|
|
this.updatePageNumber(1, EventType.ON_NEXT_PAGE);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
export default TableWidgetV2;
|