All Downloads are FREE. Search and download functionalities are using the official Maven repository.

components.form.utils.input-utils.ts Maven / Gradle / Ivy

import { getEventValue } from 'components/form/utils/validate-utils';
import { InputProps } from 'contexts/FieldInfoContext';
import { FormInfo } from 'contexts/FormInfoContext';
import { getIn } from 'formik';
import { F } from 'helpers/formatter';
import { T, TranslateFunction } from 'helpers/translator';
import { FormikType } from 'hooks/submit';
import _ from 'lodash';
import { BasicInputError, ErrorHelper, InputError, RuntimeInputError } from 'types/error';
import { FieldRenderType } from 'types/field';
import { ChoicesKind, Field, FieldType, MultipleField } from 'types/graphql';
import { Option } from 'types/option';
import Types, { SubmitValues } from 'types/types';
import { hasNonEmptyValue, isEmptyValue } from 'utils/utils';

/* traverse 'object' recursively until you find 'key', and return its value */
export function findField(object: any, key: string): any | undefined {
  if (Array.isArray(object)) {
    for (let i = 0; i < object.length; i++) {
      const value = findField(object[i], key);
      if (value !== undefined)
        return value
    }
  } else if (Types.isObject(object)) {
    for (var property in object) {
      if (object.hasOwnProperty(property)) {
        if (property === key && !(object['type'] == 'MULTIPLE')) {
          return object[property];
        } else {
          const value = findField(object[property], key)
          if (value !== undefined)
            return value
        }
      }
    }
  }

  return undefined
}

export function getPlaceholder(t: TranslateFunction, type: FieldRenderType ) {
  if (type == undefined)
    return undefined

  const key = "placeholder." + type
  const res = t(key)
  if (res == key)
    return ""
  else
    return res
}

export function toRuntimeErrorMessage(t: TranslateFunction, error: RuntimeInputError) {
  if (error === undefined)
    return undefined

  if (!error?.type)
    throw "Input error has no type"

  const err = "error.input.1"
  const res = t(err, {variables: error})
  if (res == err)
    return error.message
  else
    return res
}

export function resolveChoices(choices: any): Option[] {
  if (Array.isArray(choices)) {
    // for now we only support a direct array of the valid options
    return choices;
  }
  throw new Error(`Unsupported kind of choices '${typeof choices}': ${JSON.stringify(choices)}`)
}

export function formatChoices(translator: T, choices: Option[], kind: ChoicesKind | undefined, processKey: string): Option[] {
  const options = choices ? choices : []

  const createLabel = (option: Option) => {
    // translate
    const label = translator.toValue(option.value, option.label, processKey)!
    // change casing
    return F.toSentenceCase(label)
  }

  const reformat = kind == "STATIC" || kind == undefined
  if (reformat)
    return options.map(option => { return {...option, label : createLabel(option) } })
  else 
    return options
}

export function getFocusPathFromValues(formInfo: FormInfo, values: SubmitValues) {
  return getFocusPathFromFieldValues(formInfo.form.fields, values)
}

export function getPath(value: any, path: string) {
  if (path == "")
    return value
  else 
    return _.get(value, path)
}

type FocusOptions = {
  required?: boolean
  empty?: boolean
}

export function getFocusPathFromFieldValues(fields: Field[] | Field, values: SubmitValues) {
  function getFocus(field: Field | Field[], valuePath: string, options: FocusOptions = {}): string | undefined {
    // options: 
    // {empty: true, required: true}  : focus on first empty required field
    // {empty: true, required: false} : focus on first empty field
    // {empty: false}                 : focus on first field

    // a multiple input field
    if (Array.isArray(field)) {
      return getFocusArray(field, valuePath, options)
    } else if (Types.isObject(field) && field.hasOwnProperty('fields')) {
      return getFocusMultiple(field as MultipleField, valuePath, options)
    } else {
      return getFocusSingle(field, valuePath, options)
    }
  }
  
  function getFocusArray(fields: Field[], valuePath: string, options: FocusOptions){
    const createPath = (path: string) => path ? (valuePath ? `${valuePath}.${path}` : path) : valuePath

    for (let i = 0; i < fields.length; i++) {
      const match = getFocus(fields[i], createPath(fields[i].name), options)
      if (match)
        return match
    }
  }

  function getFocusMultiple(field: MultipleField, valuePath: string, options: FocusOptions) {
    const createPath = (path: string) => path ? (valuePath ? `${valuePath}.${path}` : path) : valuePath
    const fields     = field.fields
    const value      = getPath(values, valuePath)

    // first traverse values for empty values
    if (Array.isArray(value)) {
      for (let i = 0; i < value.length; i++) {
        const match = getFocus(fields, `${valuePath}[${i}]`, options)
        if (match)
          return match
      }
    }

    // without values, pick the first fields that has no value
    else {
      for (let i = 0; i < fields.length; i++) {
        const match = getFocus(fields[i], createPath(`[0].${fields[i].name}`), options)
        if (match)
          return match
      }
    }
  }

  function getFocusSingle(field: Field, valuePath: string, options: FocusOptions) {
    const value = _.get(values, valuePath)
    if (options.empty) {
      if (isEmptyValue(value) && (!options.required || !field.optional))
        return valuePath
    } else {
      return valuePath
    }
  }

  return getFocus(fields, "", {empty: false})
}

// we add convenience functions to props to make it more easy to work with formik
export function toAugProps({formik, t, rpath, inputProps, ...props}: {formik: FormikType, inputProps: InputProps,t: TranslateFunction, rpath: string, type: FieldType}) {
  const runtimeErrorObj = getIn(formik.status, rpath)
  const yupError        = getIn(formik.errors, rpath)

  function setRuntimeError(error: InputError | undefined) {
    formik.setStatus(_.set(formik.status, rpath, error))
  }

  function toMsg(obj: InputError): string | undefined {
    if (props.type === "MULTIPLE") {
      return hasNonEmptyValue(runtimeErrorObj) ||  hasNonEmptyValue(yupError) ? t('yup.invalid.multiple') : undefined
    } else {
      
      if (ErrorHelper.isRuntimeError(obj))
        return toRuntimeErrorMessage(t, obj as RuntimeInputError)
      else if (ErrorHelper.isBasicError(obj))
        return (obj as BasicInputError).message
      else if (obj instanceof Function)
        return toMsg(obj())
      else if (typeof obj == 'string')
        return obj.trim() !== "" ? obj : undefined
      else if (obj != null && obj != undefined)
        return "error: " + JSON.stringify(obj)
      else
        return undefined
    }
  }

  return {
    inputProps: inputProps,

    setValue:   function(value: any) { formik.setFieldValue(rpath, value)   },
    setTouched: function(value: any) { formik.setFieldTouched(rpath, value) },
    setError:   function(value: any) { formik.setFieldError(rpath, value)   },
    isError:    function()      { return this.touched && hasNonEmptyValue(this.rawError) },
    setRuntimeError: function(value: any) { setRuntimeError(value) },

    // the getters below become static after passing them as a function argument.
    get value()        { return getIn(formik.values, rpath) },
    get yupError()     { return toMsg(yupError) },
    get runtimeError() { return toMsg(runtimeErrorObj) },
    get rawError()     { return this && (this.runtimeError || this.yupError) },
    get error()        { return this.rawError },
    get touched()      { return getIn(formik.touched, rpath) },

    handleBlur: function(e: Event) {
      formik.handleBlur(e)
    },
    handleFocus: function(e: Event) {
      formik.setFieldTouched(rpath, true)
    },
    handleChange:    function(e: Event) {
      setRuntimeError(undefined)
      try {
        const value = getEventValue(e, e)
        formik.setFieldValue(rpath, value)
      } catch (error) {
        console.error("formik handle error: %o", error)
      }
    },
  }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy