Skip to content

Commit

Permalink
Add simple example for data validation
Browse files Browse the repository at this point in the history
  • Loading branch information
jassmith committed Jun 20, 2022
1 parent c15aba1 commit f597cce
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 5 deletions.
12 changes: 12 additions & 0 deletions packages/core/src/data-editor/data-editor.tsx
Expand Up @@ -193,6 +193,7 @@ export interface DataEditorProps extends Props {
readonly onHeaderContextMenu?: (colIndex: number, event: HeaderClickedEventArgs) => void;
readonly onGroupHeaderContextMenu?: (colIndex: number, event: GroupHeaderClickedEventArgs) => void;
readonly onCellContextMenu?: (cell: Item, event: CellClickedEventArgs) => void;
readonly validateCell?: (cell: Item, newValue: EditableGridCell) => boolean | EditableGridCell;

readonly columns: readonly GridColumn[];

Expand Down Expand Up @@ -353,6 +354,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
keybindings: keybindingsIn,
onRowAppended,
onColumnMoved,
validateCell: validateCellIn,
highlightRegions: highlightRegionsIn,
drawCell,
drawCustomCell,
Expand Down Expand Up @@ -438,6 +440,15 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
abortControllerRef.current
);

const validateCell = React.useCallback<NonNullable<typeof validateCellIn>>(
(cell, newValue) => {
if (validateCellIn === undefined) return true;
const item: Item = [cell[0] - rowMarkerOffset, cell[1]];
return validateCellIn?.(item, newValue);
},
[rowMarkerOffset, validateCellIn]
);

const setGridSelection = React.useCallback(
(newVal: GridSelection, expand: boolean): void => {
if (expand) {
Expand Down Expand Up @@ -2735,6 +2746,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
{overlay !== undefined && (
<DataGridOverlayEditor
{...overlay}
validateCell={validateCell}
id={overlayID}
className={p.experimental?.isSubGrid === true ? "click-outside-ignore" : undefined}
provideEditor={provideEditor}
Expand Down
Expand Up @@ -254,6 +254,43 @@ export const AddData: React.VFC = () => {
},
};

export const ValidateData: React.VFC = () => {
const { cols, getCellContent, setCellValue, getCellsForSelection } = useMockDataGenerator(60, false);

return (
<BeautifulWrapper
title="Validate data"
description={
<>
<Description>
Data can be validated using the <PropName>validateCell</PropName> callback
</Description>
<MoreInfo>This example only allows the word &quot;Valid&quot; inside text cells.</MoreInfo>
</>
}>
<DataEditor
{...defaultProps}
getCellContent={getCellContent}
columns={cols}
getCellsForSelection={getCellsForSelection}
rowMarkers={"both"}
onPaste={true}
onCellEdited={setCellValue}
rows={100}
validateCell={(_cell, newValue) => {
if (newValue.kind !== GridCellKind.Text) return true;
return newValue.data === "Valid";
}}
/>
</BeautifulWrapper>
);
};
(ValidateData as any).parameters = {
options: {
showPanel: false,
},
};

export const FillHandle: React.VFC = () => {
const { cols, getCellContent, setCellValueRaw, setCellValue, getCellsForSelection } = useMockDataGenerator(
60,
Expand Down
Expand Up @@ -6,9 +6,12 @@ import ClickOutsideContainer from "../click-outside-container/click-outside-cont
import { Theme } from "../common/styles";
import { CellRenderers } from "../data-grid/cells";
import {
EditableGridCell,
GridCell,
GridCellKind,
isEditableGridCell,
isObjectEditorCallbackResult,
Item,
ProvideEditorCallback,
Rectangle,
} from "../data-grid/data-grid-types";
Expand All @@ -20,6 +23,7 @@ type ImageEditorType = React.ComponentType<OverlayImageEditorProps>;

export interface DataGridOverlayEditorProps {
readonly target: Rectangle;
readonly cell: Item;
readonly content: GridCell;
readonly className?: string;
readonly id: string;
Expand All @@ -31,13 +35,14 @@ export interface DataGridOverlayEditorProps {
readonly imageEditorOverride?: ImageEditorType;
readonly markdownDivCreateNode?: (content: string) => DocumentFragment;
readonly provideEditor?: ProvideEditorCallback<GridCell>;
readonly validateCell?: (cell: Item, newValue: EditableGridCell) => boolean | EditableGridCell;
}

const DataGridOverlayEditor: React.FunctionComponent<DataGridOverlayEditorProps> = p => {
const {
target,
content,
onFinishEditing,
onFinishEditing: onFinishEditingIn,
forceEditMode,
initialValue,
imageEditorOverride,
Expand All @@ -46,10 +51,40 @@ const DataGridOverlayEditor: React.FunctionComponent<DataGridOverlayEditorProps>
className,
theme,
id,
cell,
validateCell,
provideEditor,
} = p;

const [tempValue, setTempValue] = React.useState<GridCell | undefined>(forceEditMode ? content : undefined);
const [tempValue, setTempValueRaw] = React.useState<GridCell | undefined>(forceEditMode ? content : undefined);

const [isValid, setIsValid] = React.useState(true);

const onFinishEditing = React.useCallback<typeof onFinishEditingIn>(
(newCell, movement) => {
onFinishEditingIn(isValid ? newCell : undefined, movement);
},
[isValid, onFinishEditingIn]
);

const setTempValue = React.useCallback(
(newVal: GridCell | undefined) => {
if (validateCell !== undefined && newVal !== undefined && isEditableGridCell(newVal)) {
const validResult = validateCell(cell, newVal);
if (validResult === false) {
setIsValid(false);
} else if (typeof validResult === "object") {
newVal = validResult;
setIsValid(true);
} else {
setIsValid(true);
}
}
setTempValueRaw(newVal);
},
[cell, validateCell]
);

const finished = React.useRef(false);
const customMotion = React.useRef<[-1 | 0 | 1, -1 | 0 | 1] | undefined>(undefined);

Expand Down Expand Up @@ -161,6 +196,7 @@ const DataGridOverlayEditor: React.FunctionComponent<DataGridOverlayEditorProps>
target={target}
imageEditorOverride={imageEditorOverride}
markdownDivCreateNode={markdownDivCreateNode}
isValid={isValid}
/>
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/data-grid/cells/cell-types.ts
Expand Up @@ -54,6 +54,7 @@ type ProvideEditorCallback<T extends InnerGridCell> = (
readonly markdownDivCreateNode?: (content: string) => DocumentFragment;
readonly target: Rectangle;
readonly forceEditMode: boolean;
readonly isValid?: boolean;
}>
| undefined;

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/data-grid/cells/text-cell.tsx
Expand Up @@ -19,7 +19,7 @@ export const textCellRenderer: InternalCellRenderer<TextCell> = {
data: "",
}),
getEditor: () => p => {
const { isHighlighted, onChange, onKeyDown, value } = p;
const { isHighlighted, onChange, onKeyDown, value, isValid } = p;
return (
<GrowingEntry
highlight={isHighlighted}
Expand All @@ -28,6 +28,7 @@ export const textCellRenderer: InternalCellRenderer<TextCell> = {
onKeyDown={onKeyDown}
altNewline={true}
value={value.data}
isValid={isValid}
onChange={e =>
onChange({
...value,
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/growing-entry/growing-entry-style.tsx
Expand Up @@ -33,6 +33,11 @@ export const InputBox = styled.textarea`
}
${inputProps}
.invalid & {
text-decoration: underline;
text-decoration-color: #d60606;
}
`;

export const ShadowBox = styled.div`
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/growing-entry/growing-entry.tsx
Expand Up @@ -8,10 +8,11 @@ interface Props
readonly placeholder?: string;
readonly highlight: boolean;
readonly altNewline?: boolean;
readonly isValid?: boolean;
}

const GrowingEntry: React.FunctionComponent<Props> = (props: Props) => {
const { placeholder, value, onKeyDown, highlight, altNewline, ...rest } = props;
const { placeholder, value, onKeyDown, highlight, altNewline, isValid, ...rest } = props;
const { onChange, className } = rest;

const inputRef = React.useRef<HTMLTextAreaElement | null>(null);
Expand Down Expand Up @@ -42,7 +43,7 @@ const GrowingEntry: React.FunctionComponent<Props> = (props: Props) => {
);

return (
<GrowingEntryStyle>
<GrowingEntryStyle className={isValid === false ? "invalid" : ""}>
<ShadowBox className={className}>{useText + "\n"}</ShadowBox>
<InputBox
{...rest}
Expand Down

0 comments on commit f597cce

Please sign in to comment.