Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
components.form.utils.field-utils.ts Maven / Gradle / Ivy
import { getPlaceholder } from 'components/form/utils/input-utils';
import { determineBasedOnWidth } from 'components/form/utils/task-utils';
import { Indices } from 'contexts/FieldInfoContext';
import { FormInfo } from 'contexts/FormInfoContext';
import { TraitHelper } from 'helpers/traits';
import _ from 'lodash';
import { FieldInfo, FieldRenderType } from 'types/field';
import { Aux, AuxProps, Field, Trait, TraitName } from 'types/graphql';
import { TaskRendering } from 'types/theming';
import Types from 'types/types';
import { getPath, isEmptyValue } from 'utils/utils';
export const fieldMinWidth = 235
export function getDefaultValue(fieldType: FieldRenderType) {
switch (fieldType) {
case "TEXT":
case "DECIMAL":
case "INTEGER":
return ""
case "MULTIPLE SELECT":
case "CHECKBOX SELECT":
return []
case "SINGLE SELECT":
case "SINGLE SELECT SUBMIT":
case "SEGMENTED BOOLEAN BUTTON":
case "RADIO SELECT":
case "RADIO BOOLEAN SELECT":
return null
case 'MULTIPLE':
return [{}]
case 'DATETIME':
case 'TIME':
case 'DATE':
return null
default:
return ""
}
}
function getValue(props: any) {
const value = props.augProps.value
if (value !== null && value !== undefined)
return props.augProps.value
return getDefaultValue(props.info.type)
}
export async function focusOnInputByClassName(className: string) {
const elements = document.querySelectorAll(`.${className} input`)
if (elements.length > 0){
// @ts-ignore
window.setTimeout(() => elements[0].focus(), 0);
}
}
export async function focusOnField(rpath: string) {
const element = document.getElementById(rpath);
if (element){
window.setTimeout(() => element.focus(), 0);
} else {
console.error("Could not focus on field with id: %o", rpath)
}
}
// get all properties relevant to the rendering of an input field
export const toFieldProps = (props: any) => {
const { t, augProps, info } = props
const isEmpty = (msg: string) => isEmptyValue(msg) || (_.isString(msg) && msg.trim().length === 0)
const msg = augProps.error
const helperText = info.inMultiple ? undefined : isEmpty(msg) ? ' ' : msg // value ' ' creates a whitespace line for the helpertext component
const inputFieldProps = info.field.type === "MULTIPLE" ? {} : {
// tabIndex : augProps.meta ? augProps.meta.tabindex : -1,
onChange : augProps.handleChange,
onBlur : augProps.handleBlur,
onFocus : augProps.handleFocus,
placeholder : getPlaceholder(t, info ? info.type : augProps.type),
...augProps.inputProps,
}
return {
id : info.rpath,
name : info.rpath,
value : getValue(props),
required : !Boolean(info.field.optional),
error : Boolean(augProps.isError()),
helperText : helperText,
...inputFieldProps
}
}
export function toValueType(value: any) {
if (!isNaN(parseInt(value)))
return "INTEGER"
else if (!isNaN(parseFloat(value)))
return "DECIMAL"
else
return "TEXT"
}
export function getField(formInfo: FormInfo, fieldPath: string): Field {
const fields = formInfo.form.fields
const field = getPath(fields, fieldPath)
if (!field) {
console.error("Could not find input field with path: %o", fieldPath)
return undefined as unknown as Field
}
return field
}
export function getAux(formInfo: FormInfo, auxPath: string): AuxProps | undefined {
return getPath(formInfo?.form?.aux, auxPath) as AuxProps
}
export function getFieldInput(formInfo: FormInfo, path: string): Field | undefined {
if (path.includes("_basedOn"))
throw "Attempting to get input field with path: '" + path + "'"
const fieldPath = toFieldPath(formInfo, path)
return getField(formInfo, fieldPath)
}
export function getFieldAux(formInfo: FormInfo, rpath: string){
const xpath = toAuxPath(formInfo, rpath)
return getAux(formInfo, xpath)
}
export function toFieldRenderType(formInfo: FormInfo, field: Field, taskRendering: TaskRendering): FieldRenderType {
const path = field.path
const rpath = replaceIndexesWith(path, "[0]")
const aux = getAux(formInfo, rpath)
const hasOptions = field.hasChoices
const inMultiple = path.includes('.')
const isDependingSelect = field.dependants?.length > 0 && hasOptions
const numOptions = field.numChoices || aux?.numChoices || 0
const forceSelect = TraitHelper.hasTrait(field, "select")
const isSelect = inMultiple || numOptions < 2 || numOptions > 3 || isDependingSelect || forceSelect
switch (field.type) {
case "MULTIPLE FILE":
return field.type as FieldRenderType
case "MULTIPLE":
return field.type
case field.type.match(/MULTIPLE.*/)?.input:
return isSelect ? "MULTIPLE SELECT" : "CHECKBOX SELECT"
case "ENTITY":
case "SELECT":
case (hasOptions || isDependingSelect) && field.type:
const hasSubmitTrait = TraitHelper.hasTrait(field, 'submit')
const isSubmitField = Types.isSubmitField(formInfo, field)
if (hasSubmitTrait && !isSubmitField)
console.error("Cannot render %o as a submit button.", rpath)
return isSubmitField ? "SINGLE SELECT SUBMIT" : isSelect ? "SINGLE SELECT" : "RADIO SELECT"
case "BOOLEAN":
if (inMultiple)
return taskRendering == 'standard' ? "SEGMENTED BOOLEAN BUTTON" : "SINGLE SELECT"
else
return "RADIO BOOLEAN SELECT"
default:
return field.type as FieldRenderType
}
}
export function replaceIndexesWith(path: string, replacement: string) {
return path.replace(/\[[^\[\]]*\]/g, replacement);
}
export function toFieldInfoPath(path: string) {
// example:
// one[2].two[3].tree => one.fields.two.fields.tree
return path.replace(/\./g, ".fields.").replace(/\[[^\[\]]*\]/g, "");
}
export function toFormFieldInfo(formInfo: FormInfo, field: Field, indices: Indices, taskRendering: TaskRendering): FieldInfo {
const rpath = toValuePath(field.path, indices)
const auxPath = toAuxPath(formInfo, rpath)
const fieldPath = toFieldPath(formInfo, rpath)
const verifyField = getField(formInfo, fieldPath)
const aux = getAux(formInfo, auxPath)
const type = toFieldRenderType(formInfo, field, taskRendering)
if (verifyField?.path != field.path)
console.error("There is a critical issue in creating field info.")
return {
field: field,
type: type,
indices: indices,
rpath: rpath,
auxPath: auxPath,
fieldPath: fieldPath,
hasOptions: field.hasChoices,
options: field?.choices ?? aux?.choices,
optionsKind: field?.choicesKind ?? aux?.choicesKind,
numOptions: (field?.numChoices ?? aux?.numChoices) || 0,
inMultiple: rpath.includes('.'),
required: !!field?.optional,
isDepending: field.dependants?.length > 0,
}
}
export function toValuePath(path: string, indices: Indices): string {
return path.replace(/\$\w+/g, (matched: string): string => {
const name = matched.substring(1)
const index = indices[name]
//console.log("renderPath(%o): name=%o, index=%o", path, name, index)
return index.toString()
});
}
// build a field path based on formInfo
export function toFieldPath(formInfo: FormInfo, path: string) {
function buildPath(fields: Field[] | undefined, parts: any[]): string[] {
if (!parts.length)
return []
const current = parts.shift()
if (current && fields) {
const name = replaceIndexesWith(current, "")
const index = fields.findIndex(field => field.name == name)
const field = fields[index]
const fpath = `[${index}]` + (parts.length > 0 ? ".fields" : "")
return [fpath, ...buildPath(field.fields, parts)]
} else
return []
}
try {
return buildPath(formInfo.form.fields, path.split(".")).join(".")
} catch (e) {
console.error("Could not create field path for path: %o", path)
throw e
}
}
export function mergeDefaults(staticDefault: any, dynamicDefault: any) {
const obj: any = {}
for (const key in staticDefault) {
const staticValue = staticDefault[key]
const dynamicValue = dynamicDefault[key]
if (dynamicValue === null || dynamicValue === undefined || dynamicValue === "") {
obj[key] = staticValue
} else if (staticValue !== undefined) {
if (Types.isObject(dynamicValue)) {
obj[key] = Types.isObject(staticValue) ? mergeDefaults(staticValue, dynamicValue) : dynamicValue
} else if (Array.isArray(dynamicValue)) {
// know that array in staticDefault is always length 1
// since it's taken from the model
obj[key] = dynamicValue.length == 0
? staticValue
: dynamicValue.map(element => mergeDefaults(staticValue[0], element))
}
} else {
obj[key] = dynamicValue
}
}
return obj
}
export function enrichValues(values: any, additionalValues: any) {
const obj: any = {}
for (const key in values) {
const value = values[key]
if (Array.isArray(value)) {
const additionalValueArray = additionalValues[key]
obj[key] = value.map((element, index) => enrichValues(element, additionalValueArray[index]))
} else {
obj[key] = value
}
}
for (const key in additionalValues) {
const value = additionalValues[key]
if (Array.isArray(value)) continue
obj[key] = value
}
return obj
}
export function collectAdditionalValues(values: any) {
const obj: any = {}
for (const key in values) {
const value = values[key]
// starting with __ indicates an additional value, e.g. __basedOn
if (key.startsWith("__")) {
obj[key] = value
} else if (Array.isArray(value)) {
obj[key] = value.map(elem => collectAdditionalValues(elem))
}
}
return obj
}
export function collectDefaultsFromFields(formInfo: FormInfo, rpath: string) {
function walkPath(fields: Field[], parts: string[]): Field | undefined {
const current = parts.shift()!
const name = replaceIndexesWith(current, "")
const field = fields?.find(field => field.name == name)
if (!parts.length) return field
else {
if (field === undefined || field.fields === undefined) return undefined
return walkPath(field?.fields, parts)
}
}
function defaultFromField(field: Field): any {
if (field.type !== "MULTIPLE") {
return field.default
}
return fieldsToObject(field.fields!)
}
function fieldsToObject(fields: Field[]): any {
const obj: any = {}
fields.forEach(subField => {
obj[subField.name] = subField.type === "MULTIPLE"
? [defaultFromField(subField)]
: subField.default
})
return obj
}
const parts = rpath.split(".").filter(x => x)
if (!parts.length) {
return fieldsToObject(formInfo.form.fields)
}
const field = walkPath(formInfo.form.fields, parts)
const obj = field === undefined ? {} : defaultFromField(field)
return obj
}
export function collectDefaultsFromAux(aux: Aux): any {
//console.log("collectDefaultsFromAux: aux=%o", aux)
const obj: any = {}
for (const key in aux) {
const value = aux[key]
if (value === null) {
obj[key] = null
} else if (Array.isArray(value)) {
obj[key] = value.map(elem => collectDefaultsFromAux(elem))
} else {
obj[key] = "default" in value ? value.default : null
}
}
return obj
}
// build a value path based on formInfo
export function toAuxPath(formInfo: FormInfo, rpath: string) {
function buildPath(fields: Field[] | undefined, parts: any[] ): string[] {
if (!parts.length)
return []
const current = parts.shift()
const name = replaceIndexesWith(current, "")
const field = fields?.find(field => field.name == name)
if (field?.type === "MULTIPLE" && field?.mode === "open")
return [replaceIndexesWith(current, "[0]"), ...buildPath(field.fields, parts)]
else
return [current, ...buildPath(field?.fields, parts)]
}
try {
return buildPath(formInfo.form.fields, rpath.split(".").filter(x => x)).join(".")
} catch (e) {
console.error("Could not create field path for path: %o", rpath)
throw e
}
}
export function createDefaultValue(formInfo: FormInfo, values: any, path: string = "") {
const nonEmpty = (value: any) => Types.isObject(value) && Object.keys(value).length > 0 || Array.isArray(value) && value.length > 0 || !Array.isArray(value) && Boolean(value)
function getValueEntry(values: any, pwd: string, key: string) {
const vpath = pwd + (pwd ? "." : "") + key
const field = getFieldInput(formInfo, vpath)
const value: any = field?.hasOwnProperty("fields") && Array.isArray(values[key])
? getValues(values[key], vpath)
: toDefaultFieldValue(formInfo, field)
return nonEmpty(value) ? [key, value] : []
}
function getValueObject(values: any, pwd: string) {
const entries = Object
.keys(values)
.filter(key => !key.startsWith('__'))
.map(key => getValueEntry(values, pwd, key))
.filter(entry => entry.length > 0)
return Object.fromEntries(entries)
}
function getValues(values: any, pwd: string): any {
if (Array.isArray(values))
return values
.map((value,index) => getValues(value, pwd + "[" + index + "]"))
.filter(nonEmpty)
else
return getValueObject(values, pwd)
}
return getValues(values ? values : formInfo.form.values, path ? path : "")
}
function toDefaultFieldValue(formInfo: FormInfo, field: Field | undefined) {
if (!field)
return null
if (field.hasOwnProperty("fields"))
throw "Default values not defined for multiple fields"
if (field.optional)
return null
// if there is only one choice, and it is a required field, select that choice
const aux = getFieldAux(formInfo, field.path)
if ((field?.numChoices ?? aux?.numChoices) == 1) {
const choices = field?.choices || aux?.choices
if (choices !== undefined) {
if (field.type.includes("MULTIPLE"))
return [choices[0]]
else
return choices[0]
}
}
return null
}
export const fieldMinWidthStyle = (formInfo: FormInfo, field: Field, taskRendering: TaskRendering) => ({ minWidth: toFieldMinWidth(formInfo, field, taskRendering).toString() + "px" })
export function toFieldMinWidth(formInfo: FormInfo, field: Field, taskRendering: TaskRendering): number {
const type = toFieldRenderType(formInfo, field, taskRendering)
const width = determineFieldWidth(formInfo, field, type, taskRendering)
return width
}
export function determineFieldWidth(formInfo: FormInfo, field: Field, type: FieldRenderType, taskRendering: TaskRendering): number {
switch (type) {
case "MULTIPLE":
// compute width of field columns
const basedOnPath = replaceIndexesWith(field.path, "[0]") + "[0].__basedOn"
const basedOn = _.get(formInfo.form.values, basedOnPath)
const basedOnWidth = determineBasedOnWidth(basedOn, true)
const widthPerField = field?.fields?.map(subfield => toFieldMinWidth(formInfo, subfield, taskRendering)) || []
// @ts-ignore
const fieldsWidth: number = widthPerField.reduce((a,b) => a+b, 0)
// compute width of action columns
const actionWidth = field.mode == "closed" ? 0 : 170
return fieldsWidth + basedOnWidth + actionWidth
case "TEXT": return 235
case "FILE": return 235
case "MULTIPLE SELECT": return 235
case "SINGLE SELECT": return 235
case "SEGMENTED BOOLEAN BUTTON": return 0
case 'DATETIME': return 200
case 'TIME': return 120
case 'DATE': return 150
case 'INTEGER': return 100
case 'DECIMAL': return 100
case 'PERIOD': return 0
default: return 235
}
}