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:
Johnson Cherian 2025-03-10 13:14:21 +05:30 committed by GitHub
commit e4ffbfe21d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 254 additions and 4 deletions

View file

@ -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

View file

@ -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">

View file

@ -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

View file

@ -624,6 +624,8 @@ class TableComponent extends React.Component {
return 'Select';
case 'newMultiSelect':
return 'Multiselect';
case 'json':
return 'JSON';
default:
capitalize(text ?? '');
}

View 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;

View file

@ -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 || '';
},