Skip to content

Commit

Permalink
MUI Support multi select autocmplete (#475)
Browse files Browse the repository at this point in the history
* MUI Support multi select autocmplete

* Fix styles. Added configsdefaultSelectWidth, defaultSearchWidth, defaultMaxRows

* lint

* fix non-autocomplete mui selects by passing object in customProps

* showCheckboxes

* nit

* 4.4.2

* lint
  • Loading branch information
ukrbublik authored Aug 11, 2021
1 parent 32aa3bb commit 12ec9fe
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 38 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Changelog
- 4.4.2
- Added support of autocomplete for multiselect widget in MUI (PR #475)
- 4.4.1
- feat: possibility to add custom operators for groups (PR #462)
- 4.4.0
Expand Down
13 changes: 8 additions & 5 deletions CONFIG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -335,11 +335,14 @@ Render settings:
|customFieldSelectProps |`{}` |You can pass props to `Select` field widget. Example: `{showSearch: true}`
|groupActionsPosition |`topRight` |You can change the position of the group actions to the following: +
`topLeft, topCenter, topRight, bottomLeft, bottomCenter, bottomRight`
|renderBeforeWidget| |
|renderAfterWidget| |
|renderBeforeActions| |
|renderAfterActions| |
|defaultSliderWidth|"200px" |Width for slider
|renderBeforeWidget | |
|renderAfterWidget | |
|renderBeforeActions | |
|renderAfterActions | |
|defaultSliderWidth |`200px` |Width for slider
|defaultSelectWidth |`200px` |Width for select
|defaultSearchWidth |`100px` |Width for search in autocomplete
|defaultMaxRows |5 | Max rows for textarea
|===

Other settings:
Expand Down
17 changes: 17 additions & 0 deletions examples/demo/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,19 @@ export default (skin: string) => {
showSearch: true
}
},
select: {
...InitialConfig.widgets.select,
},
multiselect: {
...InitialConfig.widgets.multiselect,
customProps: {
//showCheckboxes: false,
width: "200px",
input: {
width: "100px"
}
}
},
treeselect: {
...InitialConfig.widgets.treeselect,
customProps: {
Expand Down Expand Up @@ -202,6 +215,9 @@ export default (skin: string) => {
...localeSettings,

defaultSliderWidth: "200px",
defaultSelectWidth: "200px",
defaultSearchWidth: "100px",
defaultMaxRows: 5,

valueSourcesInfo: {
value: {
Expand Down Expand Up @@ -454,6 +470,7 @@ export default (skin: string) => {
label: "Colors",
type: "multiselect",
fieldSettings: {
showSearch: true,
listValues: {
yellow: "Yellow",
green: "Green",
Expand Down
4 changes: 3 additions & 1 deletion modules/components/widgets/antd/value/MultiSelect.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Select } from "antd";
import {calcTextWidth, SELECT_WIDTH_OFFSET_RIGHT} from "../../../../utils/domUtils";
import {mapListValues} from "../../../../utils/stuff";
import {useOnPropsChanged} from "../../../../utils/reactUtils";
import omit from "lodash/omit";
const Option = Select.Option;

export default class MultiSelectWidget extends PureComponent {
Expand Down Expand Up @@ -59,6 +60,7 @@ export default class MultiSelectWidget extends PureComponent {
const aValue = value && value.length ? value : undefined;
const width = aValue ? null : placeholderWidth + SELECT_WIDTH_OFFSET_RIGHT;
const dropdownWidth = this.optionsMaxWidth + SELECT_WIDTH_OFFSET_RIGHT;
const customSelectProps = omit(customProps, ["showCheckboxes"]);

return (
<Select
Expand All @@ -78,7 +80,7 @@ export default class MultiSelectWidget extends PureComponent {
value={aValue}
onChange={this.handleChange}
filterOption={this.filterOption}
{...customProps}
{...customSelectProps}
>{this.options}
</Select>
);
Expand Down
4 changes: 3 additions & 1 deletion modules/components/widgets/antd/value/Select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {calcTextWidth, SELECT_WIDTH_OFFSET_RIGHT} from "../../../../utils/domUti
import {mapListValues} from "../../../../utils/stuff";
import {useOnPropsChanged} from "../../../../utils/reactUtils";
import { Select } from "antd";
import omit from "lodash/omit";
const Option = Select.Option;

export default class SelectWidget extends PureComponent {
Expand Down Expand Up @@ -55,6 +56,7 @@ export default class SelectWidget extends PureComponent {
const dropdownWidth = this.optionsMaxWidth + SELECT_WIDTH_OFFSET_RIGHT;
const width = value ? dropdownWidth : placeholderWidth + SELECT_WIDTH_OFFSET_RIGHT;
const aValue = value != undefined ? value+"" : undefined;
const customSelectProps = omit(customProps, [""]);

return (
<Select
Expand All @@ -67,7 +69,7 @@ export default class SelectWidget extends PureComponent {
value={aValue}
onChange={this.handleChange}
filterOption={this.filterOption}
{...customProps}
{...customSelectProps}
>{this.options}
</Select>
);
Expand Down
3 changes: 1 addition & 2 deletions modules/components/widgets/antd/value/TextArea.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import { Input, Col } from "antd";
const { TextArea } = Input;
const defaultMaxRows = 5;

export default class TextAreaWidget extends PureComponent {
static propTypes = {
Expand All @@ -25,7 +24,7 @@ export default class TextAreaWidget extends PureComponent {

render() {
const {config, placeholder, customProps, value, readonly, maxLength, maxRows, fullWidth} = this.props;
const {renderSize} = config.settings;
const {renderSize, defaultMaxRows} = config.settings;
const aValue = value != undefined ? value : null;

return (
Expand Down
101 changes: 87 additions & 14 deletions modules/components/widgets/material/value/MaterialAutocomplete.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@ import TextField from "@material-ui/core/TextField";
import FormControl from "@material-ui/core/FormControl";
import Autocomplete, { createFilterOptions } from "@material-ui/lab/Autocomplete";
import CircularProgress from "@material-ui/core/CircularProgress";
import Chip from "@material-ui/core/Chip";
import Checkbox from "@material-ui/core/Checkbox";
import { makeStyles } from "@material-ui/core/styles";
import CheckBoxOutlineBlankIcon from "@material-ui/icons/CheckBoxOutlineBlank";
import CheckBoxIcon from "@material-ui/icons/CheckBox";

import useListValuesAutocomplete from "../../../../hooks/useListValuesAutocomplete";

const nonCheckedIcon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
const defaultFilterOptions = createFilterOptions();
const emptyArray = [];

export default (props) => {
const {
allowCustomValues,
allowCustomValues, multiple,
value: selectedValue, customProps, readonly, config
} = props;
const hasValue = selectedValue != null;

// hook
const {
Expand All @@ -33,22 +40,58 @@ export default (props) => {
getOptionDisabled,
getOptionLabel,
} = useListValuesAutocomplete(props, {
debounceTimeout: 100
debounceTimeout: 100,
multiple
});

// setings
const {defaultSliderWidth} = config.settings;
const {width, ...rest} = customProps || {};
const customInputProps = rest.input || {};
const customAutocompleteProps = omit(rest.autocomplete || rest, ["showSearch"]);
const {defaultSelectWidth, defaultSearchWidth} = config.settings;
const {width, showCheckboxes, ...rest} = customProps || {};
let customInputProps = rest.input || {};
const inputWidth = customInputProps.width || defaultSearchWidth;
customInputProps = omit(customInputProps, ["width"]);
const customAutocompleteProps = omit(rest, ["showSearch", "showCheckboxes"]);

const fullWidth = true;
const minWidth = width || defaultSelectWidth;
const style = {
width: (multiple ? undefined : minWidth),
minWidth: minWidth
};
const placeholder = !readonly ? aPlaceholder : "";
const hasValue = selectedValue != null;
// should be simple value to prevent re-render!s
const value = hasValue ? selectedValue : (multiple ? emptyArray : null);

const filterOptions = (options, params) => {
const filtered = defaultFilterOptions(options, params);
const extended = extendOptions(filtered, params);
return extended;
};

// Render
// styles
const useStyles = makeStyles((theme) => ({
// fix too small width
input: {
minWidth: inputWidth + " !important",
}
}));

const useStylesChip = makeStyles((theme) => ({
// fix height
root: {
height: "auto"
},
label: {
marginTop: "3px",
marginBottom: "3px",
}
}));

const classesChip = useStylesChip();
const classes = useStyles();

// render
const renderInput = (params) => {
return (
<TextField
Expand All @@ -64,35 +107,65 @@ export default (props) => {
),
}}
disabled={readonly}
placeholder={!readonly ? aPlaceholder : ""}
placeholder={placeholder}
//onChange={onInputChange}
{...customInputProps}
/>
);
};

const renderTags = (value, getTagProps) => value.map((option, index) => {
return <Chip
key={index}
classes={classesChip}
label={getOptionLabel(option)}
{...getTagProps({ index })}
/>;
});

const renderOption = (option, { selected }) => {
if (multiple && showCheckboxes != false) {
return <React.Fragment>
<Checkbox
icon={nonCheckedIcon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.title}
</React.Fragment>;
} else {
return <React.Fragment>{option.title}</React.Fragment>;
}
};

return (
<FormControl>
<FormControl fullWidth={fullWidth}>
<Autocomplete
fullWidth
style={{ width: width || defaultSliderWidth }}
disableCloseOnSelect={multiple}
fullWidth={fullWidth}
multiple={multiple}
style={style}
classes={classes}
freeSolo={allowCustomValues}
loading={isInitialLoading}
open={open}
onOpen={onOpen}
onClose={onClose}
inputValue={inputValue}
onInputChange={onInputChange}
label={!readonly ? aPlaceholder : ""}
label={placeholder}
onChange={onChange}
value={hasValue ? selectedValue : null} // should be simple value to prevent re-render!
value={value}
getOptionSelected={getOptionSelected}
disabled={readonly}
readOnly={readonly}
options={options}
getOptionLabel={getOptionLabel}
getOptionDisabled={getOptionDisabled}
renderInput={renderInput}
renderTags={renderTags}
renderOption={renderOption}
filterOptions={filterOptions}
{...customAutocompleteProps}
></Autocomplete>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default ({listValues, value, setValue, allowCustomValues, readonly, place
disabled={readonly}
readOnly={readonly}
renderValue={renderValue}
{...omit(customProps, ["showSearch"])}
{...omit(customProps, ["showSearch", "input", "showCheckboxes"])}
>
{renderOptions(hasValue ? value : [])}
</Select>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default ({listValues, value, setValue, allowCustomValues, readonly, place
disabled={readonly}
readOnly={readonly}
renderValue={renderValue}
{...omit(customProps, ["showSearch"])}
{...omit(customProps, ["showSearch", "input"])}
>
{renderOptions()}
</Select>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from "react";
import TextField from "@material-ui/core/TextField";
import FormControl from "@material-ui/core/FormControl";
const defaultMaxRows = 5;

export default (props) => {
const {value, setValue, config, readonly, placeholder, customProps, maxLength, maxRows, fullWidth} = props;
const {defaultMaxRows} = config.settings;

const onChange = e => {
let val = e.target.value;
Expand Down
5 changes: 4 additions & 1 deletion modules/config/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,10 @@ const settings = {
showSearch: true
},

defaultSliderWidth: "200px"
defaultSliderWidth: "200px",
defaultSelectWidth: "200px",
defaultSearchWidth: "100px",
defaultMaxRows: 5,
};

//----------------------------
Expand Down
6 changes: 5 additions & 1 deletion modules/config/material/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ const widgets = {
},
multiselect: {
...BasicConfig.widgets.multiselect,
factory: (props) => <MaterialMultiSelectWidget {...props} />,
factory: (props) => {
return (props.asyncFetch || props.showSearch)
? <MaterialAutocompleteWidget multiple {...props} />
: <MaterialMultiSelectWidget {...props} />;
},
},
select: {
...BasicConfig.widgets.select,
Expand Down
Loading

0 comments on commit 12ec9fe

Please sign in to comment.