Skip to content

Commit

Permalink
BREAKING CHANGE: forms now get values from context (#108)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: forms now get values from context (#108)
  • Loading branch information
matttdawson authored Nov 12, 2022
1 parent 1373903 commit 0125c5c
Show file tree
Hide file tree
Showing 17 changed files with 176 additions and 200 deletions.
42 changes: 9 additions & 33 deletions src/components/GridCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ import {
} from "./gridRender/GridRenderGenericCell";
import { ColDef, ICellEditorParams, ICellRendererParams } from "ag-grid-community";
import { GridLoadableCell } from "./GridLoadableCell";
import { GridIcon } from "../components/GridIcon";
import { GridIcon } from "./GridIcon";
import { ValueFormatterParams } from "ag-grid-community/dist/lib/entities/colDef";
import { GridPopoverContext } from "../contexts/GridPopoverContext";
import { GridPopoverContextProvider } from "../contexts/GridPopoverContextProvider";

export interface GenericCellEditorProps<E> {
multiEdit?: boolean;
editor?: (editorProps: E) => JSX.Element;
editorParams?: E; // Omit<E, keyof CellParams<IFormTestRow>>
editorParams?: E;
}

export const GridCellRenderer = (props: ICellRendererParams) => {
Expand Down Expand Up @@ -46,6 +45,7 @@ export const GridCellRenderer = (props: ICellRendererParams) => {
// This is so that typescript retains the row type to pass to the GridCells
export interface ColDefT<RowType extends GridBaseRow> extends ColDef {
_?: RowType;
editor?: (editorProps: any) => JSX.Element;
}

/*
Expand All @@ -66,7 +66,7 @@ export const GridCell = <RowType extends GridBaseRow, Props extends CellEditorCo
...(custom?.editor && {
cellClass: custom?.multiEdit ? GenericMultiEditCellClass : undefined,
editable: props.editable ?? true,
cellEditor: GenericCellEditorComponent(custom.editor),
cellEditor: GenericCellEditorComponentWrapper(custom),
}),
...(custom?.editorParams && {
cellEditorParams: { ...custom.editorParams, multiEdit: custom.multiEdit },
Expand All @@ -91,37 +91,13 @@ export interface CellEditorCommon {
className?: string | undefined;
}

export interface CellParams<RowType extends GridBaseRow> {
value: any;
data: RowType;
field: string | undefined;
selectedRows: RowType[];
}

// TODO memo?
export const GenericCellEditorComponent = (editor: (props: any) => JSX.Element) =>
forwardRef(function GenericCellEditorComponent2(props: ICellEditorParams, _) {
export const GenericCellEditorComponentWrapper = (custom?: { editor?: (props: any) => JSX.Element }) => {
return forwardRef(function GenericCellEditorComponentFr(cellEditorParams: ICellEditorParams, _) {
return (
<GridPopoverContextProvider>
<GenericCellEditorComponent3 {...{ ...props, editor }} />
<GridPopoverContextProvider props={cellEditorParams}>
{<cellEditorParams.colDef.cellRenderer {...cellEditorParams} {...cellEditorParams.colDef.cellRendererParams} />}
{custom?.editor && <custom.editor {...cellEditorParams} />}
</GridPopoverContextProvider>
);
});

const GenericCellEditorComponent3 = (props: ICellEditorParams & { editor: (props: any) => JSX.Element }) => {
const { setProps, propsRef } = useContext(GridPopoverContext);

const { colDef } = props;
const { cellEditorParams } = colDef;
const multiEdit = cellEditorParams?.multiEdit ?? false;

// TODO don't need all these props in context
setProps(props, multiEdit);

return (
<>
{<colDef.cellRenderer {...props} />}
{props?.editor && <props.editor {...cellEditorParams} {...propsRef.current} />}
</>
);
};
10 changes: 3 additions & 7 deletions src/components/GridPopoverHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface GridPopoverHookProps<RowType> {

export const useGridPopoverHook = <RowType extends GridBaseRow>(props: GridPopoverHookProps<RowType>) => {
const { stopEditing } = useContext(GridContext);
const { anchorRef, saving, propsRef } = useContext(GridPopoverContext);
const { anchorRef, saving, updateValue } = useContext(GridPopoverContext);
const saveButtonRef = useRef<HTMLButtonElement>(null);
const [isOpen, setOpen] = useState(false);

Expand All @@ -22,16 +22,12 @@ export const useGridPopoverHook = <RowType extends GridBaseRow>(props: GridPopov

const triggerSave = useCallback(
async (reason?: string) => {
if (
reason == "cancel" ||
!props.save ||
(propsRef.current?.updateValue && (await propsRef.current?.updateValue(props.save)))
) {
if (reason == "cancel" || !props.save || (updateValue && (await updateValue(props.save)))) {
setOpen(false);
stopEditing();
}
},
[props, stopEditing, propsRef],
[props.save, stopEditing, updateValue],
);

const popoverWrapper = useCallback(
Expand Down
63 changes: 28 additions & 35 deletions src/components/gridForm/GridFormDropDown.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import "../../styles/GridFormDropDown.scss";

import { MenuItem, MenuDivider, FocusableItem, MenuHeader } from "../../react-menu3";
import { useCallback, useContext, useEffect, useRef, useState, KeyboardEvent } from "react";
import { FocusableItem, MenuDivider, MenuHeader, MenuItem } from "../../react-menu3";
import { KeyboardEvent, useCallback, useContext, useEffect, useRef, useState } from "react";
import { GridBaseRow } from "../Grid";
import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
import { GridContext } from "../../contexts/GridContext";
import { delay } from "lodash-es";
import debounce from "debounce-promise";
import { CellEditorCommon, CellParams } from "../GridCell";
import { CellEditorCommon } from "../GridCell";
import { useGridPopoverHook } from "../GridPopoverHook";
import { GridPopoverContext } from "../../contexts/GridPopoverContext";

export interface GridPopoutEditDropDownSelectedItem<RowType, ValueType> {
// Note the row that was clicked on will be first
Expand Down Expand Up @@ -54,10 +55,10 @@ export interface GridFormPopoutDropDownProps<RowType extends GridBaseRow, ValueT
}

export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
_props: GridFormPopoutDropDownProps<RowType, ValueType>,
props: GridFormPopoutDropDownProps<RowType, ValueType>,
) => {
const props = _props as GridFormPopoutDropDownProps<RowType, ValueType> & CellParams<RowType>;
const { updatingCells, stopEditing } = useContext(GridContext);
const { selectedRows, field, updateValue } = useContext(GridPopoverContext);
const { stopEditing } = useContext(GridContext);

const [filter, setFilter] = useState("");
const [filteredValues, setFilteredValues] = useState<any[]>([]);
Expand All @@ -66,9 +67,8 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
const [subComponentValues, setSubComponentValues] = useState<{ optionValue: any; subComponentValue: any }[]>([]);

const selectItemHandler = useCallback(
async (value: ValueType, subComponentValue?: ValueType): Promise<boolean> => {
const field = props.field;
return await updatingCells({ selectedRows: props.selectedRows, field }, async (selectedRows) => {
async (value: ValueType, subComponentValue?: ValueType): Promise<boolean> =>
updateValue(async (selectedRows) => {
const hasChanged = selectedRows.some((row) => row[field as keyof RowType] !== value);
if (hasChanged) {
if (props.onSelectedItem) {
Expand All @@ -78,22 +78,17 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
}
}
return true;
});
},
[props, updatingCells],
}),
[field, props, updateValue],
);

const selectFilterHandler = useCallback(
async (value: string): Promise<boolean> => {
const field = props.field;
return await updatingCells({ selectedRows: props.selectedRows, field }, async (selectedRows) => {
if (props.onSelectFilter) {
await props.onSelectFilter({ selectedRows, value });
}
async (value: string) =>
updateValue(async (selectedRows) => {
props.onSelectFilter && (await props.onSelectFilter({ selectedRows, value }));
return true;
});
},
[props, updatingCells],
}),
[props, updateValue],
);

// Load up options list if it's async function
Expand All @@ -104,19 +99,19 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(

(async () => {
if (typeof optionsConf == "function") {
optionsConf = await optionsConf(props.selectedRows, filter);
optionsConf = await optionsConf(selectedRows, filter);
}

const optionsList = (optionsConf?.map((item) => {
const optionsList = optionsConf?.map((item) => {
if (item == null || typeof item == "string" || typeof item == "number") {
item = ({
value: (item as unknown) as ValueType,
item = {
value: item as unknown as ValueType,
label: item,
disabled: false,
} as unknown) as FinalSelectOption<ValueType>;
} as unknown as FinalSelectOption<ValueType>;
}
return item;
}) as any) as FinalSelectOption<ValueType>[];
}) as any as FinalSelectOption<ValueType>[];

if (props.filtered) {
// This is needed otherwise when filter input is rendered and sets autofocus
Expand All @@ -127,7 +122,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
}
optionsInitialising.current = false;
})();
}, [filter, options, props]);
}, [filter, options, props, selectedRows]);

// Local filtering.
useEffect(() => {
Expand Down Expand Up @@ -213,9 +208,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
)}
<ComponentLoadingWrapper loading={!options} className={"GridFormDropDown-options"}>
<>
{options && options.length == filteredValues?.length && (
<MenuItem key={`${props.field}-empty`}>[Empty]</MenuItem>
)}
{options && options.length == filteredValues?.length && <MenuItem key={`${field}-empty`}>[Empty]</MenuItem>}
{options?.map((item: FinalSelectOption<ValueType | string>, index) =>
item.value === MenuSeparatorString ? (
<MenuDivider key={`$$divider_${index}`} />
Expand All @@ -225,18 +218,18 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
<div key={`menu-wrapper-${index}`}>
{!item.subComponent ? (
<MenuItem
key={`${props.field}-${index}`}
key={`${field}-${index}`}
disabled={!!item.disabled}
title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
value={item.value}
onClick={() => {
selectItemHandler(item.value as ValueType);
selectItemHandler(item.value as ValueType).then();
}}
>
{item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
</MenuItem>
) : (
<FocusableItem className={"LuiDeprecatedForms"} key={`${props.field}-${index}_subcomponent`}>
<FocusableItem className={"LuiDeprecatedForms"} key={`${field}-${index}_subcomponent`}>
{(ref: any) =>
item.subComponent &&
item.subComponent(
Expand Down Expand Up @@ -273,7 +266,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
}
return false;
},
key: `${props.field}-${index}_subcomponent_inner`,
key: `${field}-${index}_subcomponent_inner`,
},
ref,
)
Expand Down
18 changes: 10 additions & 8 deletions src/components/gridForm/GridFormEditBearing.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
import "../../styles/GridFormEditBearing.scss";

import { useCallback, useState } from "react";
import { useCallback, useContext, useState } from "react";
import { GridBaseRow } from "../Grid";
import { TextInputFormatted } from "../../lui/TextInputFormatted";
import { bearingNumberParser, bearingStringValidator, convertDDToDMS } from "../../utils/bearing";
import { useGridPopoverHook } from "../GridPopoverHook";
import { CellEditorCommon, CellParams } from "../GridCell";
import { CellEditorCommon } from "../GridCell";
import { GridPopoverContext } from "../../contexts/GridPopoverContext";

export interface GridFormEditBearingProps<RowType extends GridBaseRow> extends CellEditorCommon {
placeHolder?: string;
range?: (value: number | null) => string | null;
onSave?: (selectedRows: RowType[], value: number | null) => Promise<boolean>;
}

export const GridFormEditBearing = <RowType extends GridBaseRow>(_props: GridFormEditBearingProps<RowType>) => {
const props = _props as GridFormEditBearingProps<RowType> & CellParams<RowType>;
const [value, setValue] = useState<string>(`${props.value ?? ""}`);
export const GridFormEditBearing = <RowType extends GridBaseRow>(props: GridFormEditBearingProps<RowType>) => {
const { field, value: initialValue } = useContext(GridPopoverContext);

const [value, setValue] = useState<string>(`${initialValue ?? ""}`);

const save = useCallback(
async (selectedRows: RowType[]): Promise<boolean> => {
if (bearingStringValidator(value)) return false;
const parsedValue = bearingNumberParser(value);
// Value didn't change so don't save just cancel
if (parsedValue === props.value) {
if (parsedValue === initialValue) {
return true;
}

if (props.onSave) {
return await props.onSave(selectedRows, parsedValue);
} else {
const field = props.field;
if (field == null) {
console.error("field is not defined in ColDef");
} else {
Expand All @@ -37,7 +39,7 @@ export const GridFormEditBearing = <RowType extends GridBaseRow>(_props: GridFor
}
return true;
},
[props, value],
[field, initialValue, props, value],
);
const { triggerSave, popoverWrapper } = useGridPopoverHook({ className: props.className, save });

Expand Down
16 changes: 9 additions & 7 deletions src/components/gridForm/GridFormMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import clsx from "clsx";
import { useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
import { GridBaseRow } from "../Grid";
import { useGridPopoverHook } from "../GridPopoverHook";
import { CellEditorCommon, CellParams } from "../GridCell";
import { CellEditorCommon } from "../GridCell";
import { GridPopoverContext } from "../../contexts/GridPopoverContext";

export interface GridFormMessageProps<RowType extends GridBaseRow> extends CellEditorCommon {
message: (cellParams: CellParams<RowType>) => Promise<string | JSX.Element> | string | JSX.Element;
message: (selectedRows: RowType[]) => Promise<string | JSX.Element> | string | JSX.Element;
}

export const GridFormMessage = <RowType extends GridBaseRow>(_props: GridFormMessageProps<RowType>) => {
const props = _props as GridFormMessageProps<RowType> & CellParams<RowType>;
export const GridFormMessage = <RowType extends GridBaseRow>(props: GridFormMessageProps<RowType>) => {
const { selectedRows } = useContext(GridPopoverContext);

const [message, setMessage] = useState<string | JSX.Element | null>(null);
const { popoverWrapper } = useGridPopoverHook({ className: props.className });

useEffect(() => {
(async () => {
setMessage(await props.message(props));
setMessage(await props.message(selectedRows));
})().then();
}, [props]);
}, [props, selectedRows]);

return popoverWrapper(
<ComponentLoadingWrapper loading={message === null} className={clsx("GridFormMessage-container", props.className)}>
Expand Down
Loading

0 comments on commit 0125c5c

Please sign in to comment.