admin: StringField.toField/fromField

Massimo Melina committed Mar 1, 2022 at 20:44 UTC 7ba6c729bfd310b2ffea6bc57b2a3ffe156d6049
1 file changed +33 -15
admin/src/Form.ts
+33 -15
@@ -1,4 +1,4 @@
1 -import { createElement as h, isValidElement, ReactElement, useEffect, useState } from 'react'
1 +import { createElement as h, Fragment, isValidElement, ReactElement, useEffect, useState } from 'react'
2 import {
3 Box,
4 Button,
@@ -88,15 +88,27 @@ export interface FieldProps<T> {
88 label?: string | ReactElement
89 value?: T
90 onChange: (v: T, more: { was?: T, event: any, [rest: string]: any }) => void
91 + toField?: (v: any) => T,
92 + fromField?: (v: T) => any
93 [rest: string]: any
94 }
95
96 export type Field<T> = (props:FieldProps<T>) => ReactElement
97
96 -export function StringField({ value, onChange, ...props }: FieldProps<string>) {
97 - const [state, setState] = useState(() => value ?? '')
98 - useEffect(() => setState(() => value ?? ''),
99 - [value])
98 +export function StringField({ value, onChange, fromField=_.identity, toField=_.identity, ...props }: FieldProps<string>) {
99 + if (fromField === JSON.parse)
100 + fromField = v => v ? JSON.parse(v) : undefined
101 + const [state, setState] = useState(() => toField(value) ?? '')
102 + const [err, setErr] = useState('')
103 + if (err) {
104 + props.error = true
105 + props.helperText = h(Fragment, {}, err, props.helperText && h('br'), props.helperText ) // keep existing helperText, if any
106 + }
107 +
108 + useEffect(() => {
109 + setState(() => toField(value) ?? '')
110 + setErr('')
111 + }, [value])
112 return h(TextField, {
113 fullWidth: true,
114 ...props,
@@ -113,8 +125,15 @@ export function StringField({ value, onChange, ...props }: FieldProps<string>) {
125 })
126
127 function go(event: any) {
116 - if (state !== value)
117 - onChange(state, {
128 + let newV
129 + try { // catch parsing exceptions
130 + newV = fromField(state)
131 + }
132 + catch (e) {
133 + return setErr(String(e))
134 + }
135 + if (newV !== value)
136 + onChange(newV, {
137 was: value,
138 event,
139 cancel() {
@@ -124,10 +143,8 @@ export function StringField({ value, onChange, ...props }: FieldProps<string>) {
143 }
144 }
145
127 -export function DisplayField({ map, value, empty='-', ...props }: any) {
128 - if (map)
129 - value = map(value)
130 - if (empty !== undefined && value !== 0 && !value)
146 +export function DisplayField({ value, empty='-', ...props }: any) {
147 + if (!props.toField && empty !== undefined && value !== 0 && !value)
148 value = empty
149 return h(StringField, { ...props, value, disabled: true })
150 }
@@ -198,15 +215,16 @@ export function NumberField({ value, onChange, ...props }: FieldProps<number | n
215 })
216 }
217
201 -export function BoolField({ label='', value, onChange, helperText, ...props }: FieldProps<boolean>) {
202 - const [state, setState] = useState(() => value ?? false)
203 - useEffect(() => setState(() => value ?? false),
218 +export function BoolField({ label='', value, onChange, helperText, fromField=_.identity, toField=_.identity, ...props }: FieldProps<boolean>) {
219 + const setter = () => toField(value) ?? false
220 + const [state, setState] = useState(setter)
221 + useEffect(() => setState(setter),
222 [value])
223 const control = h(Switch, {
224 checked: state,
225 ...props,
226 onChange(event) {
209 - onChange(event.target.checked, { event, was: value })
227 + onChange(fromField(event.target.checked), { event, was: value })
228 }
229 })
230 return h(Box, { ml: 1 },