diff --git a/app/client/src/assets/icons/control/download-table.svg b/app/client/src/assets/icons/control/download-table.svg
new file mode 100755
index 0000000000..cea728e494
--- /dev/null
+++ b/app/client/src/assets/icons/control/download-table.svg
@@ -0,0 +1,5 @@
+
diff --git a/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx b/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx
index 752ee42fde..9ba6441c8c 100644
--- a/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx
+++ b/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx
@@ -3,7 +3,25 @@ import { ColumnAction } from "components/propertyControls/ColumnActionSelectorCo
import Table from "./Table";
import { RenderMode, RenderModes } from "constants/WidgetConstants";
import { debounce } from "lodash";
-import { getMenuOptions, renderActions, renderCell } from "./TableUtilities";
+import {
+ getMenuOptions,
+ getAllTableColumnKeys,
+} from "components/designSystems/appsmith/TableUtilities";
+
+export enum ColumnTypes {
+ CURRENCY = "currency",
+ TIME = "time",
+ DATE = "date",
+ VIDEO = "video",
+ IMAGE = "image",
+ TEXT = "text",
+}
+
+export interface TableColumnMetaProps {
+ isHidden: boolean;
+ format?: string;
+ type: string;
+}
export interface ReactTableColumnProps {
Header: string;
@@ -12,6 +30,7 @@ export interface ReactTableColumnProps {
minWidth: number;
draggable: boolean;
isHidden?: boolean;
+ metaProperties?: TableColumnMetaProps;
Cell: (props: any) => JSX.Element;
}
@@ -36,6 +55,7 @@ export interface ColumnMenuSubOptionProps {
interface ReactTableComponentProps {
widgetId: string;
+ widgetName: string;
searchKey: string;
isDisabled?: boolean;
isVisible?: boolean;
@@ -72,140 +92,12 @@ interface ReactTableComponentProps {
handleResizeColumn: Function;
handleReorderColumn: Function;
searchTableData: (searchKey: any) => void;
+ columns: ReactTableColumnProps[];
}
const ReactTableComponent = (props: ReactTableComponentProps) => {
let dragged = -1;
- const getAllTableColumnKeys = () => {
- const tableData: object[] = props.tableData;
- const columnKeys: string[] = [];
- for (let i = 0, tableRowCount = tableData.length; i < tableRowCount; i++) {
- const row = tableData[i];
- for (const key in row) {
- if (!columnKeys.includes(key)) {
- columnKeys.push(key);
- }
- }
- }
- return columnKeys;
- };
- const reorderColumns = (columns: ReactTableColumnProps[]) => {
- const columnOrder = 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;
- };
- const getTableColumns = () => {
- const tableData: object[] = props.tableData;
- let columns: ReactTableColumnProps[] = [];
- const hiddenColumns: ReactTableColumnProps[] = [];
- if (tableData.length) {
- const columnKeys: string[] = getAllTableColumnKeys();
- for (let index = 0; index < columnKeys.length; index++) {
- const i = columnKeys[index];
- const columnName: string =
- props.columnNameMap && props.columnNameMap[i]
- ? props.columnNameMap[i]
- : i;
- const columnType: { type: string; format?: string } =
- props.columnTypeMap && props.columnTypeMap[i]
- ? props.columnTypeMap[i]
- : { type: "text" };
- const columnSize: number =
- props.columnSizeMap && props.columnSizeMap[i]
- ? props.columnSizeMap[i]
- : 150;
- const isHidden =
- !!props.hiddenColumns && 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,
- props.cell.row.index,
- columnType.type,
- isHidden,
- props.widgetId,
- columnType.format,
- );
- },
- };
- if (isHidden) {
- columnData.isHidden = true;
- hiddenColumns.push(columnData);
- } else {
- columns.push(columnData);
- }
- }
- columns = reorderColumns(columns);
- if (props.columnActions?.length) {
- columns.push({
- Header:
- props.columnNameMap && props.columnNameMap["actions"]
- ? props.columnNameMap["actions"]
- : "Actions",
- accessor: "actions",
- width: 150,
- minWidth: 60,
- draggable: true,
- Cell: () => {
- return renderActions({
- columnActions: props.columnActions,
- onCommandClick: props.onCommandClick,
- });
- },
- });
- }
- if (hiddenColumns.length && props.renderMode === RenderModes.CANVAS) {
- columns = columns.concat(hiddenColumns);
- }
- }
- return columns;
- };
-
- const tableColumns = React.useMemo(getTableColumns, [
- JSON.stringify({
- data: props.tableData,
- columnNameMap: props.columnNameMap,
- columnActions: props.columnActions,
- hiddenColumns: props.hiddenColumns,
- columnSizeMap: props.columnSizeMap,
- columnTypeMap: props.columnTypeMap,
- columnOrder: props.columnOrder,
- }),
- ]);
useEffect(() => {
const headers = Array.prototype.slice.call(
document.querySelectorAll(`#table${props.widgetId} .draggable-header`),
@@ -267,9 +159,9 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
e.preventDefault();
let columnOrder = props.columnOrder;
if (columnOrder === undefined) {
- columnOrder = getAllTableColumnKeys();
+ columnOrder = props.columns.map(item => item.accessor);
}
- const draggedColumn = tableColumns[dragged].accessor;
+ const draggedColumn = props.columns[dragged].accessor;
columnOrder.splice(dragged, 1);
columnOrder.splice(i, 0, draggedColumn);
props.handleReorderColumn(columnOrder);
@@ -281,7 +173,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
});
const getColumnMenu = (columnIndex: number) => {
- const column = tableColumns[columnIndex];
+ const column = props.columns[columnIndex];
const columnId = column.accessor;
const columnType =
props.columnTypeMap && props.columnTypeMap[columnId]
@@ -308,7 +200,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
};
const hideColumn = (columnIndex: number, isColumnHidden: boolean) => {
- const column = tableColumns[columnIndex];
+ const column = props.columns[columnIndex];
let hiddenColumns = props.hiddenColumns || [];
if (!isColumnHidden) {
hiddenColumns.push(column.accessor);
@@ -326,7 +218,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
};
const updateColumnType = (columnIndex: number, columnType: string) => {
- const column = tableColumns[columnIndex];
+ const column = props.columns[columnIndex];
const columnTypeMap = props.columnTypeMap || {};
columnTypeMap[column.accessor] = {
type: columnType,
@@ -336,7 +228,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
};
const handleColumnNameUpdate = (columnIndex: number, columnName: string) => {
- const column = tableColumns[columnIndex];
+ const column = props.columns[columnIndex];
const columnNameMap = props.columnNameMap || {};
columnNameMap[column.accessor] = columnName;
props.updateColumnName(columnNameMap);
@@ -346,7 +238,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
columnIndex: number,
currencySymbol: string,
) => {
- const column = tableColumns[columnIndex];
+ const column = props.columns[columnIndex];
const columnTypeMap = props.columnTypeMap || {};
columnTypeMap[column.accessor] = {
type: "currency",
@@ -356,7 +248,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
};
const handleDateFormatUpdate = (columnIndex: number, dateFormat: string) => {
- const column = tableColumns[columnIndex];
+ const column = props.columns[columnIndex];
const columnTypeMap = props.columnTypeMap || {};
columnTypeMap[column.accessor] = {
type: "date",
@@ -366,7 +258,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
};
const handleResizeColumn = (columnIndex: number, columnWidth: string) => {
- const column = tableColumns[columnIndex];
+ const column = props.columns[columnIndex];
const columnSizeMap = props.columnSizeMap || {};
const width = Number(columnWidth.split("px")[0]);
columnSizeMap[column.accessor] = width;
@@ -391,8 +283,9 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
height={props.height}
pageSize={props.pageSize || 1}
widgetId={props.widgetId}
+ widgetName={props.widgetName}
searchKey={props.searchKey}
- columns={tableColumns}
+ columns={props.columns}
hiddenColumns={props.hiddenColumns}
updateHiddenColumns={props.updateHiddenColumns}
data={props.tableData}
diff --git a/app/client/src/components/designSystems/appsmith/Table.tsx b/app/client/src/components/designSystems/appsmith/Table.tsx
index 37544cc2c4..49f5394103 100644
--- a/app/client/src/components/designSystems/appsmith/Table.tsx
+++ b/app/client/src/components/designSystems/appsmith/Table.tsx
@@ -26,6 +26,7 @@ interface TableProps {
height: number;
pageSize: number;
widgetId: string;
+ widgetName: string;
searchKey: string;
isLoading: boolean;
columns: ReactTableColumnProps[];
@@ -64,6 +65,9 @@ export const Table = (props: TableProps) => {
const pageCount = Math.ceil(props.data.length / props.pageSize);
const currentPageIndex = props.pageNo < pageCount ? props.pageNo : 0;
const data = React.useMemo(() => props.data, [JSON.stringify(props.data)]);
+ const columns = React.useMemo(() => props.columns, [
+ JSON.stringify(props.columns),
+ ]);
const {
getTableProps,
getTableBodyProps,
@@ -73,7 +77,7 @@ export const Table = (props: TableProps) => {
pageOptions,
} = useTable(
{
- columns: props.columns,
+ columns: columns,
data,
defaultColumn,
initialState: {
@@ -103,6 +107,8 @@ export const Table = (props: TableProps) => {
id={`table${props.widgetId}`}
>
{
pageCount={pageCount}
currentPageIndex={currentPageIndex}
pageOptions={pageOptions}
+ widgetName={props.widgetName}
serverSidePaginationEnabled={props.serverSidePaginationEnabled}
columns={props.columns.filter((column: ReactTableColumnProps) => {
return column.accessor !== "actions";
diff --git a/app/client/src/components/designSystems/appsmith/TableColumnsVisibility.tsx b/app/client/src/components/designSystems/appsmith/TableColumnsVisibility.tsx
index 0f6eab3e60..268f42d1b6 100644
--- a/app/client/src/components/designSystems/appsmith/TableColumnsVisibility.tsx
+++ b/app/client/src/components/designSystems/appsmith/TableColumnsVisibility.tsx
@@ -5,6 +5,7 @@ import {
PopoverInteractionKind,
Position,
Icon,
+ Tooltip,
} from "@blueprintjs/core";
import { IconWrapper } from "constants/IconConstants";
import styled from "styled-components";
@@ -12,19 +13,7 @@ import { Colors } from "constants/Colors";
import { ReactComponent as VisibleIcon } from "assets/icons/control/columns-visibility.svg";
import { ReactTableColumnProps } from "components/designSystems/appsmith/ReactTableComponent";
import Button from "components/editorComponents/Button";
-
-const TableIconWrapper = styled.div<{ selected?: boolean; disabled?: boolean }>`
- background: ${props => (props.selected ? Colors.ATHENS_GRAY : "transparent")};
- box-shadow: ${props =>
- props.selected ? `inset 0px 4px 0px ${Colors.GREEN}` : "none"};
- width: 48px;
- height: 60px;
- display: flex;
- align-items: center;
- justify-content: center;
- opacity: ${props => (props.disabled ? 0.6 : 1)};
- cursor: ${props => !props.disabled && "pointer"};
-`;
+import { TableIconWrapper } from "components/designSystems/appsmith/TableStyledWrappers";
const DropDownWrapper = styled.div`
display: flex;
@@ -117,13 +106,20 @@ const TableColumnsVisibility = (props: TableColumnsVisibilityProps) => {
selectMenu(true);
}}
>
-
-
-
+
+
+
+
{columns.map((option: ReactTableColumnProps, index: number) => (
diff --git a/app/client/src/components/designSystems/appsmith/TableDataDownload.tsx b/app/client/src/components/designSystems/appsmith/TableDataDownload.tsx
new file mode 100644
index 0000000000..7ee6af9dd1
--- /dev/null
+++ b/app/client/src/components/designSystems/appsmith/TableDataDownload.tsx
@@ -0,0 +1,94 @@
+import React from "react";
+import { IconWrapper } from "constants/IconConstants";
+import { Tooltip } from "@blueprintjs/core";
+import { Colors } from "constants/Colors";
+import { ReactComponent as DownloadIcon } from "assets/icons/control/download-table.svg";
+import { ReactTableColumnProps } from "components/designSystems/appsmith/ReactTableComponent";
+import { TableIconWrapper } from "components/designSystems/appsmith/TableStyledWrappers";
+
+interface TableDataDownloadProps {
+ data: object[];
+ columns: ReactTableColumnProps[];
+ widgetName: string;
+}
+
+const TableDataDownload = (props: TableDataDownloadProps) => {
+ const [selected, toggleButtonClick] = React.useState(false);
+ const downloadTableData = () => {
+ toggleButtonClick(true);
+ const csvData = [];
+ csvData.push(
+ props.columns
+ .map((column: ReactTableColumnProps) => {
+ if (column.metaProperties && !column.metaProperties.isHidden) {
+ return column.Header;
+ }
+ })
+ .filter(i => !!i),
+ );
+ for (let row = 0; row < props.data.length; row++) {
+ const data: { [key: string]: any } = props.data[row];
+ const csvDataRow = [];
+ for (let colIndex = 0; colIndex < props.columns.length; colIndex++) {
+ const column = props.columns[colIndex];
+ const value = data[column.accessor];
+ if (column.metaProperties && !column.metaProperties.isHidden) {
+ csvDataRow.push(value);
+ }
+ }
+ csvData.push(csvDataRow);
+ }
+ let csvContent = "";
+ csvData.forEach(function(infoArray, index) {
+ const dataString = infoArray.join(",");
+ csvContent += index < csvData.length ? dataString + "\n" : dataString;
+ });
+ const fileName = `${props.widgetName}.csv`;
+ const anchor = document.createElement("a");
+ const mimeType = "application/octet-stream";
+ if (navigator.msSaveBlob) {
+ navigator.msSaveBlob(
+ new Blob([csvContent], {
+ type: mimeType,
+ }),
+ fileName,
+ );
+ } else if (URL && "download" in anchor) {
+ anchor.href = URL.createObjectURL(
+ new Blob([csvContent], {
+ type: mimeType,
+ }),
+ );
+ anchor.setAttribute("download", fileName);
+ document.body.appendChild(anchor);
+ anchor.click();
+ document.body.removeChild(anchor);
+ }
+ toggleButtonClick(false);
+ };
+
+ return (
+ {
+ downloadTableData();
+ }}
+ >
+
+
+
+
+
+
+ );
+};
+
+export default TableDataDownload;
diff --git a/app/client/src/components/designSystems/appsmith/TableHeader.tsx b/app/client/src/components/designSystems/appsmith/TableHeader.tsx
index 2afdb285ea..92b6ac5e0b 100644
--- a/app/client/src/components/designSystems/appsmith/TableHeader.tsx
+++ b/app/client/src/components/designSystems/appsmith/TableHeader.tsx
@@ -1,9 +1,6 @@
import React from "react";
import styled from "styled-components";
import { Icon, NumericInput } from "@blueprintjs/core";
-import SearchComponent from "components/designSystems/appsmith/SearchComponent";
-import TableColumnsVisibility from "components/designSystems/appsmith/TableColumnsVisibility";
-import { ReactTableColumnProps } from "components/designSystems/appsmith/ReactTableComponent";
import {
RowWrapper,
PaginationWrapper,
@@ -11,6 +8,10 @@ import {
PaginationItemWrapper,
CommonFunctionsMenuWrapper,
} from "./TableStyledWrappers";
+import SearchComponent from "components/designSystems/appsmith/SearchComponent";
+import TableColumnsVisibility from "components/designSystems/appsmith/TableColumnsVisibility";
+import { ReactTableColumnProps } from "components/designSystems/appsmith/ReactTableComponent";
+import TableDataDownload from "components/designSystems/appsmith/TableDataDownload";
import { Colors } from "constants/Colors";
const PageNumberInputWrapper = styled(NumericInput)`
@@ -58,12 +59,15 @@ interface TableHeaderProps {
nextPageClick: () => void;
prevPageClick: () => void;
pageNo: number;
+ tableData: object[];
+ tableColumns: ReactTableColumnProps[];
pageCount: number;
currentPageIndex: number;
pageOptions: number[];
columns: ReactTableColumnProps[];
hiddenColumns?: string[];
updateHiddenColumns: (hiddenColumns?: string[]) => void;
+ widgetName: string;
searchKey: string;
searchTableData: (searchKey: any) => void;
serverSidePaginationEnabled: boolean;
@@ -79,6 +83,11 @@ const TableHeader = (props: TableHeaderProps) => {
onSearch={props.searchTableData}
/>
+
{props.displayColumnActions && (
`
- width: ${props => props.width - 5}px;
- height: ${props => props.height - 5}px;
+ width: 100%;
+ height: 100%;
background: white;
border: 1px solid ${Colors.GEYSER_LIGHT};
box-sizing: border-box;
@@ -21,26 +21,14 @@ export const TableWrapper = styled.div<{ width: number; height: number }>`
border-spacing: 0;
color: ${Colors.BLUE_BAYOUX};
position: relative;
- .thead {
- overflow-y: auto;
- overflow-x: hidden;
- }
+ overflow-y: auto;
+ height: ${props => props.height - TABLE_SIZES.TABLE_HEADER_HEIGHT}px;
+ .thead,
.tbody {
- overflow: scroll;
- /*
- Here 5px is subtracted to compensate padding from widget resizers and
- 113px to compensate table column header and table header heights
- */
- height: ${props =>
- props.height -
- 5 -
- TABLE_SIZES.TABLE_HEADER_HEIGHT -
- TABLE_SIZES.COLUMN_HEADER_HEIGHT}px;
- &.no-scroll {
- overflow: hidden;
- }
+ overflow: hidden;
}
.tr {
+ overflow: hidden;
:nth-child(even) {
background: ${Colors.ATHENS_GRAY_DARKER};
}
@@ -285,3 +273,22 @@ export const RowWrapper = styled.div`
margin: 0 4px;
white-space: nowrap;
`;
+
+export const TableIconWrapper = styled.div<{
+ selected?: boolean;
+ disabled?: boolean;
+}>`
+ background: ${props => (props.selected ? Colors.ATHENS_GRAY : "transparent")};
+ box-shadow: ${props =>
+ props.selected ? `inset 0px 4px 0px ${Colors.GREEN}` : "none"};
+ width: 48px;
+ height: 60px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: ${props => (props.disabled ? 0.6 : 1)};
+ cursor: ${props => !props.disabled && "pointer"};
+ &:hover {
+ background: ${Colors.ATHENS_GRAY};
+ }
+`;
diff --git a/app/client/src/components/designSystems/appsmith/TableUtilities.tsx b/app/client/src/components/designSystems/appsmith/TableUtilities.tsx
index aff1054514..e4e6efc9ea 100644
--- a/app/client/src/components/designSystems/appsmith/TableUtilities.tsx
+++ b/app/client/src/components/designSystems/appsmith/TableUtilities.tsx
@@ -1,18 +1,22 @@
import React, { useState } from "react";
import { Icon, InputGroup } from "@blueprintjs/core";
-import moment from "moment-timezone";
import {
MenuColumnWrapper,
CellWrapper,
ActionWrapper,
} from "./TableStyledWrappers";
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
-import { ColumnMenuOptionProps } from "./ReactTableComponent";
-import { isString } from "lodash";
+import {
+ ColumnMenuOptionProps,
+ ReactTableColumnProps,
+ ColumnTypes,
+} from "components/designSystems/appsmith/ReactTableComponent";
+import { isString, isNumber } from "lodash";
import VideoComponent from "components/designSystems/appsmith/VideoComponent";
import Button from "components/editorComponents/Button";
import AutoToolTipComponent from "components/designSystems/appsmith/AutoToolTipComponent";
import TableColumnMenuPopup from "./TableColumnMenu";
+import { Colors } from "constants/Colors";
interface MenuOptionProps {
columnAccessor?: string;
@@ -57,86 +61,106 @@ export const getMenuOptions = (props: MenuOptionProps) => {
},
{
content: (
-
+
Image
),
closeOnClick: true,
- isSelected: props.columnType === "image",
+ isSelected: props.columnType === ColumnTypes.IMAGE,
onClick: (columnIndex: number, isSelected: boolean) => {
if (isSelected) {
props.updateColumnType(columnIndex, "");
} else {
- props.updateColumnType(columnIndex, "image");
+ props.updateColumnType(columnIndex, ColumnTypes.IMAGE);
}
},
},
{
content: (
-
+
Video
),
- isSelected: props.columnType === "video",
+ isSelected: props.columnType === ColumnTypes.VIDEO,
closeOnClick: true,
onClick: (columnIndex: number, isSelected: boolean) => {
if (isSelected) {
props.updateColumnType(columnIndex, "");
} else {
- props.updateColumnType(columnIndex, "video");
+ props.updateColumnType(columnIndex, ColumnTypes.VIDEO);
}
},
},
{
content: (
-
+
Text
),
closeOnClick: true,
- isSelected: props.columnType === "text",
+ isSelected: props.columnType === ColumnTypes.TEXT,
onClick: (columnIndex: number, isSelected: boolean) => {
if (isSelected) {
props.updateColumnType(columnIndex, "");
} else {
- props.updateColumnType(columnIndex, "text");
+ props.updateColumnType(columnIndex, ColumnTypes.TEXT);
}
},
},
{
content: (
-
+
Currency
),
closeOnClick: false,
- isSelected: props.columnType === "currency",
+ isSelected: props.columnType === ColumnTypes.CURRENCY,
options: [
{
content: "USD - $",
@@ -198,23 +222,31 @@ export const getMenuOptions = (props: MenuOptionProps) => {
},
{
content: (
-
+
Date
),
closeOnClick: false,
- isSelected: props.columnType === "date",
+ isSelected: props.columnType === ColumnTypes.DATE,
options: [
{
content: "MM-DD-YY",
@@ -252,22 +284,26 @@ export const getMenuOptions = (props: MenuOptionProps) => {
},
{
content: (
-
+
Time
),
closeOnClick: true,
- isSelected: props.columnType === "time",
+ isSelected: props.columnType === ColumnTypes.TIME,
onClick: (columnIndex: number, isSelected: boolean) => {
if (isSelected) {
props.updateColumnType(columnIndex, "");
} else {
- props.updateColumnType(columnIndex, "time");
+ props.updateColumnType(columnIndex, ColumnTypes.TIME);
}
},
},
@@ -277,17 +313,11 @@ export const getMenuOptions = (props: MenuOptionProps) => {
export const renderCell = (
value: any,
- rowIndex: number,
columnType: string,
isHidden: boolean,
- widgetId: string,
- format?: string,
) => {
- if (!value) {
- return ;
- }
switch (columnType) {
- case "image":
+ case ColumnTypes.IMAGE:
if (!isString(value)) {
return (
@@ -316,7 +346,7 @@ export const renderCell = (
})}
);
- case "video":
+ case ColumnTypes.VIDEO:
const youtubeRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|\?v=)([^#\&\?]*).*/;
if (isString(value) && youtubeRegex.test(value)) {
return (
@@ -329,65 +359,11 @@ export const renderCell = (
Invalid Video Link
);
}
- case "currency":
- if (!isNaN(value)) {
- return (
- {`${format}${value}`}
- );
- } else {
- return Invalid Value;
- }
- case "date":
- let isValidDate = true;
- if (isNaN(value)) {
- const dateTime = Date.parse(value);
- if (isNaN(dateTime)) {
- isValidDate = false;
- }
- }
- if (isValidDate) {
- return (
-
- {moment(value).format(format)}
-
- );
- } else {
- return Invalid Date;
- }
- case "time":
- let isValidTime = true;
- if (isNaN(value)) {
- const time = Date.parse(value);
- if (isNaN(time)) {
- isValidTime = false;
- }
- }
- if (isValidTime) {
- return (
-
- {moment(value).format("HH:mm")}
-
- );
- } else {
- return Invalid Time;
- }
- case "text":
- const text = isString(value) ? value : JSON.stringify(value);
- return (
-
- {text}
-
- );
default:
- const data = isString(value) ? value : JSON.stringify(value);
+ const data =
+ isString(value) || isNumber(value) ? value : JSON.stringify(value);
return (
-
+
{data}
);
@@ -586,3 +562,51 @@ export const TableHeaderCell = (props: {
);
};
+
+export const getAllTableColumnKeys = (tableData: object[]) => {
+ const columnKeys: string[] = [];
+ for (let i = 0, tableRowCount = tableData.length; i < tableRowCount; i++) {
+ const row = tableData[i];
+ for (const key in row) {
+ if (!columnKeys.includes(key)) {
+ columnKeys.push(key);
+ }
+ }
+ }
+ return columnKeys;
+};
+
+export const reorderColumns = (
+ columns: ReactTableColumnProps[],
+ columnOrder: string[],
+) => {
+ 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;
+};
diff --git a/app/client/src/widgets/TableWidget.tsx b/app/client/src/widgets/TableWidget.tsx
index 3eb6ef4c80..7ea372d2dd 100644
--- a/app/client/src/widgets/TableWidget.tsx
+++ b/app/client/src/widgets/TableWidget.tsx
@@ -2,9 +2,19 @@ import React, { Suspense } from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/ActionConstants";
-import ReactTableComponent from "components/designSystems/appsmith/ReactTableComponent";
+import ReactTableComponent, {
+ ReactTableColumnProps,
+ ColumnTypes,
+} from "components/designSystems/appsmith/ReactTableComponent";
+import {
+ getAllTableColumnKeys,
+ renderCell,
+ renderActions,
+ reorderColumns,
+} from "components/designSystems/appsmith/TableUtilities";
import { TABLE_SIZES } from "components/designSystems/appsmith/Table";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
+import { RenderMode, RenderModes } from "constants/WidgetConstants";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
@@ -12,6 +22,7 @@ import {
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
import Skeleton from "components/utils/Skeleton";
+import moment from "moment";
class TableWidget extends BaseWidget {
static getPropertyValidationMap(): WidgetPropertyValidationType {
@@ -51,6 +62,141 @@ class TableWidget extends BaseWidget {
};
}
+ getTableColumns = (tableData: object[]) => {
+ let columns: ReactTableColumnProps[] = [];
+ const hiddenColumns: ReactTableColumnProps[] = [];
+ const {
+ columnNameMap,
+ columnSizeMap,
+ columnTypeMap,
+ widgetId,
+ columnActions,
+ } = this.props;
+ if (tableData.length) {
+ const columnKeys: string[] = getAllTableColumnKeys(tableData);
+ for (let index = 0; index < columnKeys.length; index++) {
+ const i = columnKeys[index];
+ const columnName: string =
+ columnNameMap && columnNameMap[i] ? columnNameMap[i] : i;
+ const columnType: { type: string; format?: string } =
+ columnTypeMap && columnTypeMap[i]
+ ? columnTypeMap[i]
+ : { type: ColumnTypes.TEXT };
+ const columnSize: number =
+ columnSizeMap && columnSizeMap[i] ? 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,
+ metaProperties: {
+ isHidden: isHidden,
+ type: columnType.type,
+ format: columnType.format,
+ },
+ Cell: (props: any) => {
+ return renderCell(props.cell.value, columnType.type, isHidden);
+ },
+ };
+ if (isHidden) {
+ columnData.isHidden = true;
+ hiddenColumns.push(columnData);
+ } else {
+ columns.push(columnData);
+ }
+ }
+ columns = reorderColumns(columns, this.props.columnOrder || []);
+ if (columnActions?.length) {
+ columns.push({
+ Header:
+ columnNameMap && columnNameMap["actions"]
+ ? columnNameMap["actions"]
+ : "Actions",
+ accessor: "actions",
+ width: 150,
+ minWidth: 60,
+ draggable: true,
+ Cell: () => {
+ return renderActions({
+ columnActions: columnActions,
+ onCommandClick: this.onCommandClick,
+ });
+ },
+ });
+ }
+ if (
+ hiddenColumns.length &&
+ this.props.renderMode === RenderModes.CANVAS
+ ) {
+ columns = columns.concat(hiddenColumns);
+ }
+ }
+ return columns;
+ };
+
+ transformData = (tableData: object[], columns: ReactTableColumnProps[]) => {
+ const updatedTableData = [];
+ for (let row = 0; row < tableData.length; row++) {
+ const data: { [key: string]: any } = tableData[row];
+ const tableRow: { [key: string]: any } = {};
+ for (let colIndex = 0; colIndex < columns.length; colIndex++) {
+ const column = columns[colIndex];
+ const { accessor } = column;
+ const value = data[accessor];
+ if (column.metaProperties) {
+ const type = column.metaProperties.type;
+ const format = column.metaProperties.format;
+ switch (type) {
+ case ColumnTypes.CURRENCY:
+ if (!isNaN(value)) {
+ tableRow[accessor] = `${format}${value ? value : ""}`;
+ } else {
+ tableRow[accessor] = "Invalid Value";
+ }
+ break;
+ case ColumnTypes.DATE:
+ let isValidDate = true;
+ if (isNaN(value)) {
+ const dateTime = Date.parse(value);
+ if (isNaN(dateTime)) {
+ isValidDate = false;
+ }
+ }
+ if (isValidDate) {
+ tableRow[accessor] = moment(value).format(format);
+ } else {
+ tableRow[accessor] = "Invalid Value";
+ }
+ break;
+ case ColumnTypes.TIME:
+ let isValidTime = true;
+ if (isNaN(value)) {
+ const time = Date.parse(value);
+ if (isNaN(time)) {
+ isValidTime = false;
+ }
+ }
+ if (isValidTime) {
+ tableRow[accessor] = moment(value).format("HH:mm");
+ } else {
+ tableRow[accessor] = "Invalid Value";
+ }
+ break;
+ default:
+ tableRow[accessor] = value;
+ break;
+ }
+ }
+ }
+ updatedTableData.push(tableRow);
+ }
+ return updatedTableData;
+ };
+
searchTableData = (tableData: object[]) => {
const searchKey =
this.props.searchKey !== undefined
@@ -66,8 +212,9 @@ class TableWidget extends BaseWidget {
getPageView() {
const { tableData, hiddenColumns } = this.props;
+ const tableColumns = this.getTableColumns(tableData);
const filteredTableData = this.searchTableData(tableData);
-
+ const transformedData = this.transformData(filteredTableData, tableColumns);
const serverSidePaginationEnabled = (this.props
.serverSidePaginationEnabled &&
this.props.serverSidePaginationEnabled) as boolean;
@@ -94,9 +241,11 @@ class TableWidget extends BaseWidget {