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

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

import {
  selectOption,
  isOption,
  isSelectedOption,
  filterDuplicateOptions,
  filterOptionsByLabelWithExpression
} from "utils/option-utils";
import * as React from "react";
import { CircularProgress } from "@mui/material";
import Checkbox from "@mui/material/Checkbox";
import TextField from "@mui/material/TextField";
import Autocomplete from "@mui/material/Autocomplete";
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
import CheckBoxIcon from "@mui/icons-material/CheckBox";
import { formatChoices } from "components/form/utils/input-utils";
import InputError from "components/form/fields/InputError";
import { validateField } from "components/form/utils/validate-utils";
import { useDebounce, withDefault } from "utils/utils";
import { Chip, Typography } from "@mui/material";
import { useFieldInfo } from "hooks/field";
import { useFormInfo } from "hooks/form";
import { useTranslator } from "hooks/translator";
import { useAutoSubmitSignal } from "hooks/autosubmit";
import { useConfig } from "hooks/config";
import { fieldMinWidthStyle } from "components/form/utils/field-utils";

const InputMultipleSelectFieldContent = (props) => {
  const { augProps, fieldProps, info } = useFieldInfo()
  const formInfo                       = useFormInfo()
  const { t, translator }              = useTranslator()

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


  const { setFocus, setViewOpen }       = props
  const loadInitialOptions = open && allOptions.length === 0;
  const fancySelect        = allOptions.length < 100
  
  const getOptions = () => formatChoices(translator, info.options, info.optionsKind, formInfo.processDefinition.key)
  
  // set selected
  React.useEffect(() => {
    var selected = fieldProps.value && Array.isArray(fieldProps.value) ? fieldProps.value : []

    // convert values to options
    if (selected.length > 0 && !isOption(selected[0]))
      selected = selected.map(value => selectOption(info.options, value))

    // translate options
    selected = formatChoices(translator, selected, info.optionsKind, formInfo.processDefinition.key)
    setSelected(selected)
  }, [fieldProps.value]);

  // load initial options
  React.useEffect(() => {
    if (!loadInitialOptions)
      return

    setLoading(true)

    let active = true;
    (async () => {
      const options = formatChoices(translator, info.options, info.optionsKind, formInfo.processDefinition.key)
      if (active)
        setAllOptions(options)
    })();

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

  // filter options
  React.useEffect(() => {
    if (!(open && allOptions.length == info.numOptions))
      return;

    setLoading(true)

    let active = true;
    (async () => {
      const filteredOptions = debouncedInput === ""
        ? allOptions
        : filterOptionsByLabelWithExpression(allOptions, debouncedInput)

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

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

  // update current load state
  React.useEffect(() => {
    if (!open) {
      setAllOptions([]);
      setLoading(false)
    } else {
      if (options.length > 100)
        setOptions([]);

      if (input !== debouncedInput)
        setLoading(true)

      if (input === "" || fancySelect)
        skipDebounce()
    }
  }, [input, open])

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

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

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

  const getSelected = (value, previousSelected, allowRemoval, reason) => {
    const matchesOption = (option) => option.value === value?.value || option.value === value
    const addValueTo    = (selected) => {
      if (selected.some(matchesOption))
        return allowRemoval ? selected.filter(o => !matchesOption(o)) : selected
      else
        return [...selected, value]
    }

    if (Array.isArray(value))
      switch (reason) {
        case "removeOption":
          return value
        case "selectOption":
          return value
        default:
          return filterDuplicateOptions(withDefault(value, []))
      }
    else {
        const selected = !Array.isArray(previousSelected) ? [] : previousSelected
        return value ? addValueTo(selected) : selected
    }
  }


  function handleChange(e, value, reason) {
    augProps.setRuntimeError(undefined)
    const newSelected = getSelected(value, selected, true, reason)
    signal()
    
    setSelected(newSelected)
    augProps.setValue(newSelected)
    handleValidate(e, newSelected)
  }

  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

        e.preventDefault()
        e.stopPropagation()

        const option = highlighted
        const tabAlreadyMatches = optionMatches(option, input)
        if (tabAlreadyMatches){
          handleChange(e, option)
        } 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)
  }

  const renderOption = (props, option) => (
    
  • } checkedIcon={} style={{ marginRight: 8 }} checked={isSelectedOption(option, selected)} /> {option.label}
  • ) const renderOptions = fancySelect ? { renderOption: renderOption } : {} const { onChange, ...localFieldProps } = fieldProps return ( x} // highlight options autoHighlight highlighted={highlighted} 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} isOptionEqualToValue={(option, value) => value && option.value === value.value } onInputChange={handleInputChange} input={input} {...renderOptions} renderTags={(value, getTagProps) => value.map((option, index) => {option.label}} /> ) } renderInput={(params) => { params.inputProps.onKeyDown = handleKeyDown; 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 InputMultipleSelectFieldLocal = (props) => ( ) export default InputMultipleSelectFieldLocal




    © 2015 - 2024 Weber Informatics LLC | Privacy Policy