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

components.form.fields.InputSingleSelectFieldLocal.js Maven / Gradle / Ivy

There is a newer version: 0.80.3
Show newest version
import {
  selectOption,
  isOption,
  filterOptionsByLabelWithExpression
} from "utils/option-utils";
import * as React from "react";
import { formatChoices } from 'components/form/utils/input-utils';
import { Autocomplete } from '@mui/material';
import { CircularProgress } from "@mui/material";
import TextField from "@mui/material/TextField";
import { isEmptyValue, useDebounce, withDefault } from "utils/utils";
import { validateField } from "components/form/utils/validate-utils";
import InputError from "components/form/fields/InputError";
import { useFieldInfo } from "hooks/field";
import { useFormInfo } from "hooks/form";
import { useNotifier } from "hooks/notification";
import { useTranslator } from "hooks/translator";
import { useAutoSubmitSignal } from "hooks/autosubmit";
import { useConfig } from "hooks/config";
import { fieldMinWidthStyle } from "components/form/utils/field-utils";

const InputSingleSelectFieldContent = (props) => {
  const { augProps, fieldProps, info } = useFieldInfo()
  const { valueType }                  = props.selectProps
  const formInfo                       = useFormInfo()
  const processKey                     = formInfo.processDefinition.key 
  const { t, translator }              = useTranslator()

  const [input, setInput, debouncedInput, skipDebounce] = useDebounce("", 500)
  const [open, setOpen]                                 = React.useState(false);
  const [options, setOptions]                           = React.useState(info.options || []);
  const [selected, setSelected]                         = React.useState(null);                // this value is never allowed to be 'undefined'!!
  const [highlighted, setHighlighted]                   = React.useState(null)
  const [loading, setLoading ]                          = React.useState(false)
  const notifier                                        = useNotifier()
  const { setFocus, setViewOpen }                       = props
  const {signal}                                        = useAutoSubmitSignal()
  const {props: {taskRendering}}                        = useConfig()

  const getOptions = () => formatChoices(translator, info.options, info.optionsKind, processKey)
  const getOption  = (valueOrOption) => {
    const option = isOption(valueOrOption) ? valueOrOption : selectOption(info.options, valueOrOption)
    if (!option) {
      notifier.error("Unable to obtain the proper option for field " + info.field.label + " given selected value: " + JSON.stringify(selected))
      console.log("selected %o is not in options %o", selected, info.options)
      return null
    } else 
      return { value: option.value, label: translator.toValue(option.value, option.label, processKey) }
  }

  // set selected value
  React.useEffect(() => {
    const selected = fieldProps.value
    if (isEmptyValue(selected))
      setSelected(null)
    else {
      const option = getOption(selected)
      setSelected(option)
     
      switch (valueType) {
        case "option":
            if (!isEmptyValue(selected) && !isOption(selected)){
              console.debug("Select field %o will convert a value to an option", info.rpath)
              handleChange(null, option)
            }
            break
        case "value":
            if (!isEmptyValue(fieldProps.value) && isOption(fieldProps.value)){
              console.debug("Select field %o will convert a option to a value", info.rpath)
              handleChange(null, option)
            }
            break
        default:
      }

    }
  }, [fieldProps.value]);

  // filter options
  React.useEffect(() => {
    if (!(open))
      return;

    setLoading(true)

    let active = true;
    (async () => {
      const options = getOptions()
      const filteredOptions = debouncedInput === "" || debouncedInput === selected?.label
        ? options
        : filterOptionsByLabelWithExpression(options, debouncedInput)

      if (active) {
        setOptions(filteredOptions)
        setLoading(false)
      }
    })();

    return () => { active = false; }
  }, [open, debouncedInput]);

  // update current load state
  React.useEffect(() => {
    if (!open) {
      setOptions([]);
      setLoading(false)
    } else {
      if (input !== debouncedInput)
        setLoading(true)

      skipDebounce()
    }
  }, [input, open])

  function handleValidate(e, option) {
    const error = validateField("select", fieldProps.required, e, option)
    augProps.setError(error)
  }

  function handleBlur (e) {
    setFocus(false)
    fieldProps.onBlur(e)
    handleValidate(e, selected)
    signal()
  }

  function handleFocus(e) {
    setFocus(true)
    fieldProps.onFocus(e)
  }

  // store selected option locally and with formik. This HAS to be in two places, as a workaround for re-render behavior.
  function handleChange(e, value) {
    augProps.setRuntimeError(undefined)
    const option = withDefault(value, null)
    setSelected(option)

    const formikValue = valueType == 'value' && isOption(option) ? option.value : option
    augProps.setValue(formikValue)
    handleValidate(e, option)
  }

  function handleInputChange(e, value) {
    setInput(value)
  }

  function optionMatches(option, value) {
    return Boolean(option?.label?.toLowerCase().includes(input?.toLowerCase() || ""))
  }

  function handleTabSelection(e, value) {
    const options = getOptions()
    const opt     = options.find(option => optionMatches(option, value))

    if (opt){
      handleChange(e, opt)
    } else {
      notifier.error("no option matches input: " + value)
      e.stopPropagation()
      e.preventDefault()
    }
  }

  function handleKeyDown(e) {
    switch (e.key) {
      case "Tab": // tab completion
        if (input == "")
          return

        const option = highlighted
        const tabAlreadyMatches = optionMatches(option, input)
        if (tabAlreadyMatches){
          handleChange(e, option)
        } else if(optionMatches(selected, input)) {
          return 
        } else {
          skipDebounce()
          handleTabSelection(e, input)
        }
        break;

      case "Enter": // enter completion
        e.preventDefault();
        if (input !== debouncedInput) {
          e.stopPropagation()
          skipDebounce()
        }
        break;

      default:
    }
  }

  function handleHightlightChange(e, value) {
    setHighlighted(value)
  }

  function getOptionLabel(option) {
    return option.label || 'ERROR: No label for option ' + JSON.stringify(option)
  }

  function getOptionKey(option) {
    return option.value
  }

  const { onChange, ...localFieldProps } = fieldProps
  return (
     x}
      isOptionEqualToValue={(option, value) => option.value === value?.value}
      getOptionLabel={getOptionLabel}
      getOptionKey={getOptionKey}

      // highlight options
      autoHighlight
      onHighlightChange={handleHightlightChange}

      sx={{flexGrow: 1}}//width: taskRendering == 'standard' ? "100%": undefined}}
      componentsProps={{paper: {sx: {minWidth: "100%", width: "max-content"}}}} // This increases the width of the dropdown box

      // popper options
      open={open}
      onOpen={()  => { setOpen(true); setViewOpen(true) }}
      onClose={() => { setOpen(false); setViewOpen(false) }}

      // localization options
      loadingText={t("select.loading")}
      clearText={t("select.clear")}
      closeText={t("select.close")}
      openText={t("select.open")}
      noOptionsText={t("select.nooptions")}

      // input value options
      value={selected}
      loading={loading}
      onChange={handleChange}
      onInputChange={handleInputChange}
      inputValue={input ? input : ""}

      renderInput={(params) => {
        params.inputProps.onKeyDown = handleKeyDown; // add onKey event here, because it is overwritten if you give it to 'autocomplete'
        return (
          
                  {loading ?  : null}
                  {params.InputProps.endAdornment}
                
              ),
              inputProps: {
                ...params.inputProps,
                "data-state": "local"
              }
            }}
            size={fieldProps.size}
            style={taskRendering == 'standard' ? fieldMinWidthStyle(formInfo, info.field) : undefined}

            fullWidth
          />
        )
      }}
    />
  );
}

const InputSingleSelectFieldLocal = (props) => (
  
    
  
)

export default InputSingleSelectFieldLocal




© 2015 - 2024 Weber Informatics LLC | Privacy Policy