mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-06 06:48:21 +00:00
Merge pull request #12175 from ToolJet/feat/table-json-datatype
Feature: Added support for JSON column type in Table component
This commit is contained in:
commit
e4ffbfe21d
6 changed files with 254 additions and 4 deletions
|
|
@ -52,6 +52,7 @@ export const PropertiesTabElements = ({
|
|||
{ label: 'Boolean', value: 'boolean' },
|
||||
{ label: 'Image', value: 'image' },
|
||||
{ label: 'Link', value: 'link' },
|
||||
{ label: 'JSON', value: 'json' },
|
||||
// Following column types are deprecated
|
||||
{ label: 'Default', value: 'default' },
|
||||
{ label: 'Dropdown', value: 'dropdown' },
|
||||
|
|
@ -266,6 +267,24 @@ export const PropertiesTabElements = ({
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
{column.columnType === 'json' && (
|
||||
<div className="border mx-3 column-popover-card-ui" style={{ borderRadius: '6px', marginTop: '-8px' }}>
|
||||
<div style={{ background: 'var(--surfaces-surface-02)', padding: '8px 12px' }}>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Indent"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="jsonIndentation"
|
||||
props={column}
|
||||
component={component}
|
||||
paramMeta={{ type: 'toggle', displayName: 'Indent' }}
|
||||
paramType="properties"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="border mx-3 column-popover-card-ui" style={{ borderRadius: '6px', marginTop: '-8px' }}>
|
||||
<div style={{ background: 'var(--surfaces-surface-02)', padding: '8px 12px' }}>
|
||||
<ProgramaticallyHandleProperties
|
||||
|
|
|
|||
|
|
@ -122,9 +122,18 @@ export const StylesTabElements = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{['string', 'default', undefined, 'number', 'boolean', 'select', 'text', 'newMultiSelect', 'datepicker'].includes(
|
||||
column.columnType
|
||||
) && (
|
||||
{[
|
||||
'string',
|
||||
'default',
|
||||
undefined,
|
||||
'number',
|
||||
'json',
|
||||
'boolean',
|
||||
'select',
|
||||
'text',
|
||||
'newMultiSelect',
|
||||
'datepicker',
|
||||
].includes(column.columnType) && (
|
||||
<>
|
||||
{column.columnType !== 'boolean' && (
|
||||
<div data-cy={`input-and-label-text-color`} className="field px-3">
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ export const ProgramaticallyHandleProperties = ({
|
|||
return props?.parseInUnixTimestamp;
|
||||
case 'isDateSelectionEnabled':
|
||||
return props?.isDateSelectionEnabled;
|
||||
case 'jsonIndentation':
|
||||
return props?.jsonIndentation;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
|
@ -81,6 +83,9 @@ export const ProgramaticallyHandleProperties = ({
|
|||
if (property === 'linkColor') {
|
||||
return definitionObj?.value ?? '#1B1F24';
|
||||
}
|
||||
if (property === 'jsonIndentation') {
|
||||
return definitionObj?.value ?? `{{true}}`;
|
||||
}
|
||||
return definitionObj?.value ?? `{{false}}`;
|
||||
};
|
||||
|
||||
|
|
@ -111,7 +116,9 @@ export const ProgramaticallyHandleProperties = ({
|
|||
const fxActiveFieldsPropExists = props?.hasOwnProperty('fxActiveFields') ?? false;
|
||||
//to support backward compatibility, when fxActive is true for a particular column, we are passing all possible combinations which should render codehinter
|
||||
const fxActive =
|
||||
props?.fxActive && resolveReferences(props.fxActive) ? ['isEditable', 'columnVisibility', 'linkTarget'] : [];
|
||||
props?.fxActive && resolveReferences(props.fxActive)
|
||||
? ['isEditable', 'columnVisibility', 'jsonIndentation', 'linkTarget']
|
||||
: [];
|
||||
|
||||
const checkFxActiveFieldIsArrray = (fxActiveFieldsProperty) => {
|
||||
// adding error handling mechanism for fxActiveFieldsProperty , if props.fxActiveFields is array , then return props.fxActiveFields or else return [], this will make sure, fxActiveFields wil always be array
|
||||
|
|
|
|||
|
|
@ -624,6 +624,8 @@ class TableComponent extends React.Component {
|
|||
return 'Select';
|
||||
case 'newMultiSelect':
|
||||
return 'Multiselect';
|
||||
case 'json':
|
||||
return 'JSON';
|
||||
default:
|
||||
capitalize(text ?? '');
|
||||
}
|
||||
|
|
|
|||
191
frontend/src/AppBuilder/Widgets/Table/Json.jsx
Normal file
191
frontend/src/AppBuilder/Widgets/Table/Json.jsx
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/* eslint-disable no-undef */
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { determineJustifyContentValue } from '@/_helpers/utils';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
|
||||
const Json = ({
|
||||
isEditable,
|
||||
jsonIndentation,
|
||||
darkMode,
|
||||
handleCellValueChange,
|
||||
cellTextColor,
|
||||
cellValue,
|
||||
column,
|
||||
containerWidth,
|
||||
cell,
|
||||
horizontalAlignment,
|
||||
isMaxRowHeightAuto,
|
||||
cellSize,
|
||||
maxRowHeightValue,
|
||||
}) => {
|
||||
const ref = React.useRef(null);
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const cellStyles = {
|
||||
color: cellTextColor ?? 'inherit',
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditable && isEditing) {
|
||||
setIsEditing(false);
|
||||
}
|
||||
}, [isEditable]);
|
||||
|
||||
function format(obj) {
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return typeof obj === 'string' ? `"${obj}"` : obj;
|
||||
}
|
||||
if (Array.isArray(obj)) {
|
||||
return `[ ${obj.map(format).join(', ')} ]`;
|
||||
}
|
||||
return `{ ${Object.entries(obj)
|
||||
.map(([key, value]) => `"${key}": ${format(value)}`)
|
||||
.join(', ')} }`;
|
||||
}
|
||||
|
||||
const formatCellValue = (value, overlay = false) => {
|
||||
try {
|
||||
if (typeof value === 'object') {
|
||||
if (jsonIndentation === true && !overlay) {
|
||||
return JSON.stringify(value, null, 4).replace(/":/g, '": ');
|
||||
}
|
||||
const formattedJSON = format(value);
|
||||
return formattedJSON;
|
||||
} else {
|
||||
if (jsonIndentation === true && !overlay) {
|
||||
return JSON.stringify(JSON.parse(value), null, 4).replace(/":/g, '": ');
|
||||
}
|
||||
const formattedJSON = format(JSON.parse(value));
|
||||
return formattedJSON;
|
||||
}
|
||||
} catch (error) {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
const _renderString = () => (
|
||||
<div
|
||||
ref={ref}
|
||||
contentEditable={'true'}
|
||||
className={`h-100 text-container long-text-input d-flex align-items-center ${
|
||||
darkMode ? ' textarea-dark-theme' : ''
|
||||
} justify-content-${determineJustifyContentValue(horizontalAlignment)}`}
|
||||
tabIndex={-1}
|
||||
style={{
|
||||
color: cellTextColor ? cellTextColor : 'inherit',
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
background: 'inherit',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
}}
|
||||
readOnly={!isEditable}
|
||||
onBlur={(e) => {
|
||||
setIsEditing(false);
|
||||
if (cellValue !== e.target.textContent) {
|
||||
const value = JSON.stringify(JSON.parse(e.target.textContent.replace(/\n/g, '')));
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
ref.current.blur();
|
||||
if (cellValue !== e.target.textContent) {
|
||||
const value = JSON.stringify(JSON.parse(e.target.textContent.replace(/\n/g, '')));
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
setIsEditing(true);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{String(formatCellValue(cellValue))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const getOverlay = () => {
|
||||
return (
|
||||
<div
|
||||
className={`overlay-cell-table ${darkMode && 'dark-theme'}`}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
style={{ color: 'var(--text-primary)' }}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
width: `${containerWidth}px`,
|
||||
whiteSpace: 'pre-wrap',
|
||||
}}
|
||||
>
|
||||
{String(formatCellValue(cellValue, true))}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const _showOverlay =
|
||||
ref?.current &&
|
||||
(ref?.current?.clientWidth < ref?.current?.children[0]?.offsetWidth ||
|
||||
ref?.current?.clientHeight < ref?.current?.children[0]?.offsetHeight);
|
||||
|
||||
return (
|
||||
<>
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={_showOverlay ? getOverlay() : <div></div>}
|
||||
trigger={_showOverlay && ['hover', 'focus']}
|
||||
rootClose={true}
|
||||
show={_showOverlay && hovered && !isEditing}
|
||||
>
|
||||
{!isEditable ? (
|
||||
<div
|
||||
className={`d-flex align-items-center h-100 w-100 justify-content-${determineJustifyContentValue(
|
||||
horizontalAlignment
|
||||
)}`}
|
||||
style={cellStyles}
|
||||
onMouseMove={() => {
|
||||
if (!hovered) setHovered(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHovered(false);
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
maxHeight: isMaxRowHeightAuto
|
||||
? 'fit-content'
|
||||
: maxRowHeightValue
|
||||
? `${maxRowHeightValue}px`
|
||||
: cellSize === 'condensed'
|
||||
? '39px'
|
||||
: '45px',
|
||||
whiteSpace: 'pre-wrap',
|
||||
}}
|
||||
>
|
||||
{String(formatCellValue(cellValue))}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-100 d-flex flex-column justify-content-center position-relative">
|
||||
<div
|
||||
onMouseMove={() => {
|
||||
if (!hovered) setHovered(true);
|
||||
}}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
className={`${isEditing ? 'h-100 content-editing' : ''} h-100`}
|
||||
>
|
||||
{_renderString()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</OverlayTrigger>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Json;
|
||||
|
|
@ -14,6 +14,7 @@ import { CustomSelect } from '../CustomSelect';
|
|||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import Text from '../Text';
|
||||
import StringColumn from '../String';
|
||||
import Json from '../Json';
|
||||
|
||||
export default function generateColumnsData({
|
||||
columnProperties,
|
||||
|
|
@ -714,6 +715,27 @@ export default function generateColumnsData({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
case 'json': {
|
||||
const jsonIndentation = getResolvedValue(column?.jsonIndentation) ?? true;
|
||||
return (
|
||||
<Json
|
||||
isEditable={isEditable}
|
||||
jsonIndentation={jsonIndentation}
|
||||
darkMode={darkMode}
|
||||
handleCellValueChange={handleCellValueChange}
|
||||
cellTextColor={cellTextColor}
|
||||
horizontalAlignment={horizontalAlignment}
|
||||
cellValue={cellValue}
|
||||
column={column}
|
||||
currentState={currentState}
|
||||
containerWidth={width}
|
||||
cell={cell}
|
||||
isMaxRowHeightAuto={isMaxRowHeightAuto}
|
||||
cellSize={cellSize}
|
||||
maxRowHeightValue={maxRowHeightValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return cellValue || '';
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue