PromucFlow_constructor/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx

489 lines
15 KiB
TypeScript
Raw Normal View History

2020-06-03 10:50:10 +00:00
import React from "react";
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
import Table from "./Table";
import { RenderMode, RenderModes } from "constants/WidgetConstants";
import _ from "lodash";
import { getMenuOptions, renderActions, renderCell } from "./TableUtilities";
interface ReactTableComponentState {
trigger: number;
columnIndex: number;
pageSize: number;
action: string;
columnName: string;
}
export interface ReactTableColumnProps {
Header: string;
accessor: string;
width: number;
minWidth: number;
draggable: boolean;
isHidden?: boolean;
Cell: (props: any) => JSX.Element;
}
export interface ColumnMenuOptionProps {
content: string | JSX.Element;
closeOnClick?: boolean;
isSelected?: boolean;
columnAccessor?: string;
id?: string;
category?: boolean;
options?: ColumnMenuSubOptionProps[];
onClick?: (isSelected: boolean) => void;
}
export interface ColumnMenuSubOptionProps {
content: string;
isSelected: boolean;
closeOnClick: boolean;
onClick: () => void;
}
interface ReactTableComponentProps {
widgetId: string;
isDisabled?: boolean;
isVisible?: boolean;
renderMode: RenderMode;
width: number;
height: number;
pageSize: number;
tableData: object[];
columnOrder?: string[];
disableDrag: (disable: boolean) => void;
onRowClick: (rowData: object, rowIndex: number) => void;
onCommandClick: (dynamicTrigger: string) => void;
updatePageNo: Function;
updateHiddenColumns: Function;
resetSelectedRowIndex: Function;
nextPageClick: Function;
prevPageClick: Function;
pageNo: number;
serverSidePaginationEnabled: boolean;
columnActions?: ColumnAction[];
selectedRowIndex: number;
hiddenColumns?: string[];
columnNameMap?: { [key: string]: string };
columnTypeMap?: {
[key: string]: {
type: string;
format: string;
};
};
columnSizeMap?: { [key: string]: number };
updateColumnType: Function;
updateColumnName: Function;
handleResizeColumn: Function;
handleReorderColumn: Function;
}
export class ReactTableComponent extends React.Component<
ReactTableComponentProps,
ReactTableComponentState
> {
private dragged = -1;
constructor(props: ReactTableComponentProps) {
super(props);
this.state = {
trigger: 0,
columnIndex: -1,
action: "",
columnName: "",
pageSize: props.pageSize,
};
}
componentDidMount() {
this.mountEvents();
}
componentDidUpdate(prevProps: ReactTableComponentProps) {
if (this.props.pageSize !== prevProps.pageSize) {
this.setState({ pageSize: this.props.pageSize });
}
this.mountEvents();
}
mountEvents() {
const headers = Array.prototype.slice.call(
document.querySelectorAll(
`#table${this.props.widgetId} .draggable-header`,
),
);
const columns = this.getTableColumns();
headers.forEach((header, i) => {
header.setAttribute("draggable", true);
header.ondragstart = (e: React.DragEvent<HTMLDivElement>) => {
header.style =
"background: #efefef; border-radius: 4px; z-index: 100; width: 100%; text-overflow: none; overflow: none;";
e.stopPropagation();
this.dragged = i;
};
header.ondrag = (e: React.DragEvent<HTMLDivElement>) => {
e.stopPropagation();
};
header.ondragend = (e: React.DragEvent<HTMLDivElement>) => {
header.style = "";
e.stopPropagation();
setTimeout(() => (this.dragged = -1), 1000);
};
// the dropped header
header.ondragover = (e: React.DragEvent<HTMLDivElement>) => {
if (i !== this.dragged && this.dragged !== -1) {
if (this.dragged > i) {
header.parentElement.className = "th header-reorder highlight-left";
} else if (this.dragged < i) {
header.parentElement.className =
"th header-reorder highlight-right";
}
}
e.preventDefault();
};
header.ondragenter = (e: React.DragEvent<HTMLDivElement>) => {
if (i !== this.dragged && this.dragged !== -1) {
if (this.dragged > i) {
header.parentElement.className = "th header-reorder highlight-left";
} else if (this.dragged < i) {
header.parentElement.className =
"th header-reorder highlight-right";
}
}
e.preventDefault();
};
header.ondragleave = (e: React.DragEvent<HTMLDivElement>) => {
header.parentElement.className = "th header-reorder";
e.preventDefault();
};
header.ondrop = (e: React.DragEvent<HTMLDivElement>) => {
header.style = "";
header.parentElement.className = "th header-reorder";
if (i !== this.dragged && this.dragged !== -1) {
e.preventDefault();
let columnOrder = this.props.columnOrder;
if (columnOrder === undefined) {
columnOrder = this.props.tableData.length
? Object.keys(this.props.tableData[0])
: [];
}
const draggedColumn = columns[this.dragged].accessor;
columnOrder.splice(this.dragged, 1);
columnOrder.splice(i, 0, draggedColumn);
this.props.handleReorderColumn(columnOrder);
this.setState({ trigger: Math.random() });
} else {
this.dragged = -1;
}
};
});
}
getTableColumns = () => {
const tableData: object[] = this.props.tableData;
let columns: ReactTableColumnProps[] = [];
const hiddenColumns: ReactTableColumnProps[] = [];
if (tableData.length) {
const row = tableData[0];
for (const i in row) {
const columnName: string =
this.props.columnNameMap && this.props.columnNameMap[i]
? this.props.columnNameMap[i]
: i;
const columnType: { type: string; format?: string } =
this.props.columnTypeMap && this.props.columnTypeMap[i]
? this.props.columnTypeMap[i]
: { type: "text" };
const columnSize: number =
this.props.columnSizeMap && this.props.columnSizeMap[i]
? this.props.columnSizeMap[i]
: 150;
const isHidden =
!!this.props.hiddenColumns && this.props.hiddenColumns.includes(i);
const columnData = {
Header: columnName,
accessor: i,
width: columnSize,
minWidth: 60,
draggable: true,
isHidden: false,
Cell: (props: any) => {
return renderCell(
props.cell.value,
2020-06-16 07:37:39 +00:00
props.cell.row.index,
2020-06-03 10:50:10 +00:00
columnType.type,
isHidden,
2020-06-16 07:37:39 +00:00
this.props.widgetId,
2020-06-03 10:50:10 +00:00
columnType.format,
);
},
};
if (isHidden) {
columnData.isHidden = true;
hiddenColumns.push(columnData);
} else {
columns.push(columnData);
}
}
columns = this.reorderColumns(columns);
if (this.props.columnActions?.length) {
columns.push({
2020-06-16 07:37:39 +00:00
Header:
this.props.columnNameMap && this.props.columnNameMap["actions"]
? this.props.columnNameMap["actions"]
: "Actions",
2020-06-03 10:50:10 +00:00
accessor: "actions",
width: 150,
minWidth: 60,
draggable: true,
Cell: () => {
return renderActions({
columnActions: this.props.columnActions,
onCommandClick: this.props.onCommandClick,
});
},
});
}
if (
hiddenColumns.length &&
this.props.renderMode === RenderModes.CANVAS
) {
columns = columns.concat(hiddenColumns);
}
}
return columns;
};
reorderColumns = (columns: ReactTableColumnProps[]) => {
const columnOrder = this.props.columnOrder || [];
const reorderedColumns = [];
const reorderedFlagMap: { [key: string]: boolean } = {};
for (let index = 0; index < columns.length; index++) {
const accessor = columnOrder[index];
if (accessor) {
const column = columns.filter((col: ReactTableColumnProps) => {
return col.accessor === accessor;
});
if (column.length && !reorderedFlagMap[column[0].accessor]) {
reorderedColumns.push(column[0]);
reorderedFlagMap[column[0].accessor] = true;
} else if (!reorderedFlagMap[columns[index].accessor]) {
reorderedColumns.push(columns[index]);
reorderedFlagMap[columns[index].accessor] = true;
}
} else if (!reorderedFlagMap[columns[index].accessor]) {
reorderedColumns.push(columns[index]);
reorderedFlagMap[columns[index].accessor] = true;
}
}
if (reorderedColumns.length < columns.length) {
for (let index = 0; index < columns.length; index++) {
if (!reorderedFlagMap[columns[index].accessor]) {
reorderedColumns.push(columns[index]);
reorderedFlagMap[columns[index].accessor] = true;
}
}
}
return reorderedColumns;
};
showMenu = (columnIndex: number) => {
this.setState({ columnIndex: columnIndex, action: "" });
};
getColumnMenu = () => {
const { columnIndex } = this.state;
let columnType = "";
let columnId = "";
let format = "";
if (columnIndex !== -1) {
const columns = this.getTableColumns();
const column = columns[columnIndex];
columnId = column.accessor;
columnType =
this.props.columnTypeMap && this.props.columnTypeMap[columnId]
? this.props.columnTypeMap[columnId].type
: "";
format =
this.props.columnTypeMap && this.props.columnTypeMap[columnId]
? this.props.columnTypeMap[columnId].format
: "";
}
const isColumnHidden = !!(
this.props.hiddenColumns && this.props.hiddenColumns.includes(columnId)
);
const columnMenuOptions: ColumnMenuOptionProps[] = getMenuOptions({
columnAccessor: columnId,
isColumnHidden,
columnType,
format,
hideColumn: this.hideColumn,
updateColumnType: this.updateColumnType,
handleUpdateCurrencySymbol: this.handleUpdateCurrencySymbol,
handleDateFormatUpdate: this.handleDateFormatUpdate,
updateAction: (action: string) => this.setState({ action: action }),
});
return columnMenuOptions;
};
hideColumn = (isColumnHidden: boolean) => {
const columns = this.getTableColumns();
const columnIndex = this.state.columnIndex;
const column = columns[columnIndex];
let hiddenColumns = this.props.hiddenColumns || [];
if (!isColumnHidden) {
hiddenColumns.push(column.accessor);
const columnOrder = this.props.columnOrder || [];
if (columnOrder.includes(column.accessor)) {
columnOrder.splice(columnOrder.indexOf(column.accessor), 1);
this.props.handleReorderColumn(columnOrder);
}
} else {
hiddenColumns = hiddenColumns.filter(item => {
return item !== column.accessor;
});
}
this.props.updateHiddenColumns(hiddenColumns);
this.setState({ columnIndex: -1 });
};
updateColumnType = (columnType: string) => {
const columns = this.getTableColumns();
const columnIndex = this.state.columnIndex;
const column = columns[columnIndex];
const columnTypeMap = this.props.columnTypeMap || {};
columnTypeMap[column.accessor] = {
type: columnType,
format: "",
};
this.props.updateColumnType(columnTypeMap);
this.setState({ action: "", columnIndex: -1 });
};
onColumnNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ columnName: event.target.value });
};
onKeyPress = (key: string) => {
if (key === "Enter") {
this.handleColumnNameUpdate();
}
};
handleColumnNameUpdate = () => {
const columns = this.getTableColumns();
const columnIndex = this.state.columnIndex;
const columnName = this.state.columnName;
const column = columns[columnIndex];
const columnNameMap = this.props.columnNameMap || {};
columnNameMap[column.accessor] = columnName;
this.props.updateColumnName(columnNameMap);
this.setState({
columnIndex: -1,
columnName: "",
action: "",
});
};
handleUpdateCurrencySymbol = (currencySymbol: string) => {
const columns = this.getTableColumns();
const columnIndex = this.state.columnIndex;
const column = columns[columnIndex];
const columnTypeMap = this.props.columnTypeMap || {};
columnTypeMap[column.accessor] = {
type: "currency",
format: currencySymbol,
};
this.props.updateColumnType(columnTypeMap);
this.setState({
columnIndex: -1,
action: "",
});
};
handleDateFormatUpdate = (dateFormat: string) => {
const columns = this.getTableColumns();
const columnIndex = this.state.columnIndex;
const column = columns[columnIndex];
const columnTypeMap = this.props.columnTypeMap || {};
columnTypeMap[column.accessor] = {
type: "date",
format: dateFormat,
};
this.props.updateColumnType(columnTypeMap);
this.setState({
columnIndex: -1,
action: "",
});
};
handleResizeColumn = (columnIndex: number, columnWidth: string) => {
const columns = this.getTableColumns();
const column = columns[columnIndex];
const columnSizeMap = this.props.columnSizeMap || {};
const width = Number(columnWidth.split("px")[0]);
columnSizeMap[column.accessor] = width;
this.props.handleResizeColumn(columnSizeMap);
};
selectTableRow = (
row: { original: object; index: number },
isSelected: boolean,
) => {
if (!isSelected) {
this.props.onRowClick(row.original, row.index);
} else {
this.props.resetSelectedRowIndex();
}
};
render() {
const columns = this.getTableColumns();
return (
<Table
width={this.props.width}
height={this.props.height}
pageSize={this.state.pageSize || 1}
widgetId={this.props.widgetId}
columns={columns}
data={this.props.tableData}
showMenu={this.showMenu}
displayColumnActions={this.props.renderMode === RenderModes.CANVAS}
columnNameMap={this.props.columnNameMap}
columnMenuOptions={this.getColumnMenu()}
columnIndex={this.state.columnIndex}
columnAction={this.state.action}
onColumnNameChange={this.onColumnNameChange}
handleColumnNameUpdate={this.handleColumnNameUpdate}
handleResizeColumn={_.debounce(this.handleResizeColumn, 300)}
selectTableRow={this.selectTableRow}
pageNo={this.props.pageNo - 1}
updatePageNo={this.props.updatePageNo}
nextPageClick={() => {
this.props.nextPageClick();
}}
prevPageClick={() => {
this.props.prevPageClick();
}}
onKeyPress={this.onKeyPress}
serverSidePaginationEnabled={this.props.serverSidePaginationEnabled}
selectedRowIndex={this.props.selectedRowIndex}
disableDrag={() => {
this.props.disableDrag(true);
}}
enableDrag={() => {
this.props.disableDrag(false);
}}
/>
);
}
}
export default ReactTableComponent;