diff --git a/geonode_mapstore_client/client/js/components/Autocomplete/Autocomplete.jsx b/geonode_mapstore_client/client/js/components/Autocomplete/Autocomplete.jsx
index f2745456f0..5b17b83f31 100644
--- a/geonode_mapstore_client/client/js/components/Autocomplete/Autocomplete.jsx
+++ b/geonode_mapstore_client/client/js/components/Autocomplete/Autocomplete.jsx
@@ -14,17 +14,12 @@ import SelectInfiniteScroll from '@js/components/SelectInfiniteScroll/SelectInfi
const Autocomplete = ({
className,
- clearable = false,
id,
labelKey,
- multi = false,
name,
title,
value,
valueKey,
- placeholder,
- onChange,
- onLoadOptions,
...props
}) => {
const getValue = () => {
@@ -39,6 +34,12 @@ const Autocomplete = ({
}
return value;
};
+
+ const defaultNewOptionCreator = (option) => ({
+ [valueKey]: option.label,
+ [labelKey]: option.label
+ });
+
return (
@@ -46,11 +47,11 @@ const Autocomplete = ({
{...props}
id={id}
value={getValue()}
- multi={multi}
- clearable={clearable}
- placeholder={placeholder}
- loadOptions={onLoadOptions}
- onChange={onChange}
+ valueKey={valueKey}
+ labelKey={labelKey}
+ {...props.creatable && {
+ newOptionCreator: props.newOptionCreator ?? defaultNewOptionCreator
+ }}
/>
);
@@ -58,17 +59,12 @@ const Autocomplete = ({
Autocomplete.propTypes = {
className: PropTypes.string,
- clearable: PropTypes.bool,
id: PropTypes.string.isRequired,
labelKey: PropTypes.string,
- multi: PropTypes.bool,
name: PropTypes.string,
title: PropTypes.string,
value: PropTypes.any.isRequired,
- valueKey: PropTypes.string,
- placeholder: PropTypes.string,
- onChange: PropTypes.func.isRequired,
- onLoadOptions: PropTypes.func.isRequired
+ valueKey: PropTypes.string
};
export default Autocomplete;
diff --git a/geonode_mapstore_client/client/js/components/SelectInfiniteScroll/SelectInfiniteScroll.jsx b/geonode_mapstore_client/client/js/components/SelectInfiniteScroll/SelectInfiniteScroll.jsx
index e05fa7a958..cd1c251755 100644
--- a/geonode_mapstore_client/client/js/components/SelectInfiniteScroll/SelectInfiniteScroll.jsx
+++ b/geonode_mapstore_client/client/js/components/SelectInfiniteScroll/SelectInfiniteScroll.jsx
@@ -9,6 +9,7 @@
import React, { useRef, useState, useEffect } from 'react';
import axios from '@mapstore/framework/libs/ajax';
import debounce from 'lodash/debounce';
+import isEmpty from 'lodash/isEmpty';
import ReactSelect from 'react-select';
import localizedProps from '@mapstore/framework/components/misc/enhancers/localizedProps';
@@ -18,6 +19,9 @@ function SelectInfiniteScroll({
loadOptions,
pageSize = 20,
debounceTime = 500,
+ labelKey,
+ valueKey,
+ newOptionPromptText = "Create option",
...props
}) {
@@ -40,6 +44,23 @@ function SelectInfiniteScroll({
source.current = cancelToken.source();
};
+ const updateNewOption = (newOptions, query) => {
+ if (props.creatable && !isEmpty(query)) {
+ const isValueExist = props.value?.some(v => v[labelKey] === query);
+ const isOptionExist = newOptions.some((o) => o[labelKey] === query);
+
+ // Add new option if it doesn't exist and `creatable` is enabled
+ if (!isValueExist && !isOptionExist) {
+ return [{
+ [labelKey]: `${newOptionPromptText} "${query}"`, value: query,
+ result: { [valueKey]: query, [labelKey]: query }
+ }].concat(newOptions);
+ }
+ return newOptions;
+ }
+ return newOptions;
+ };
+
const handleUpdateOptions = useRef();
handleUpdateOptions.current = (args = {}) => {
createToken();
@@ -56,8 +77,10 @@ function SelectInfiniteScroll({
}
})
.then((response) => {
- const newOptions = response.results.map(({ selectOption }) => selectOption);
- setOptions(newPage === 1 ? newOptions : [...options, ...newOptions]);
+ let newOptions = response.results.map(({ selectOption }) => selectOption);
+ newOptions = newPage === 1 ? newOptions : [...options, ...newOptions];
+ newOptions = updateNewOption(newOptions, query);
+ setOptions(newOptions);
setIsNextPageAvailable(response.isNextPageAvailable);
setLoading(false);
source.current = undefined;
@@ -89,7 +112,7 @@ function SelectInfiniteScroll({
handleUpdateOptions.current({ q: value, page: 1 });
}
}, debounceTime);
- }, []);
+ }, [text]);
useEffect(() => {
if (open) {
@@ -106,6 +129,13 @@ function SelectInfiniteScroll({
}
}, [page]);
+ const filterOptions = (currentOptions) => {
+ return currentOptions.map(option=> {
+ const match = /\"(.*?)\"/.exec(text);
+ return match ? match[1] : option;
+ });
+ };
+
return (
setOpen(true)}
onClose={() => setOpen(false)}
- filterOptions={(currentOptions) => {
- return currentOptions;
- }}
+ filterOptions={filterOptions}
onInputChange={(q) => handleInputChange(q)}
onMenuScrollToBottom={() => {
if (!loading && isNextPageAvailable) {
diff --git a/geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_fields/SchemaField.jsx b/geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_fields/SchemaField.jsx
index 002d22b632..f757f53fe1 100644
--- a/geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_fields/SchemaField.jsx
+++ b/geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_fields/SchemaField.jsx
@@ -42,17 +42,20 @@ const SchemaField = (props) => {
const valueKey = autocompleteOptions?.valueKey || 'id';
const labelKey = autocompleteOptions?.labelKey || 'label';
const placeholder = autocompleteOptions?.placeholder ?? '...';
+ const creatable = !!autocompleteOptions?.creatable;
let autoCompleteProps = {
+ className: "form-group gn-metadata-autocomplete",
+ clearable: !isMultiSelect,
+ creatable,
id: idSchema.$id,
+ labelKey,
+ multi: isMultiSelect,
name,
+ placeholder,
title: schema.title,
value: formData,
valueKey,
- labelKey,
- placeholder,
- multi: isMultiSelect,
- clearable: !isMultiSelect,
onChange: (selected) => {
let _selected = selected?.result ?? null;
if (isMultiSelect) {
@@ -67,39 +70,34 @@ const SchemaField = (props) => {
});
}
onChange(_selected);
+ },
+ loadOptions: ({ q, config, ...params }) => {
+ return axios.get(autocompleteUrl, {
+ ...config,
+ params: {
+ ...params,
+ ...(q && { [queryKey]: q }),
+ page: params.page
+ }
+ })
+ .then(({ data }) => {
+ return {
+ isNextPageAvailable: !!data.pagination?.more,
+ results: data?.[resultsKey].map((result) => {
+ return {
+ selectOption: {
+ result,
+ value: result[valueKey],
+ label: result[labelKey]
+ }
+ };
+ })
+ };
+ });
}
};
- return (
- {
- return axios.get(autocompleteUrl, {
- ...config,
- params: {
- ...params,
- ...(q && { [queryKey]: q }),
- page: params.page
- }
- })
- .then(({ data }) => {
- return {
- isNextPageAvailable: !!data.pagination?.more,
- results: data?.[resultsKey].map((result) => {
- return {
- selectOption: {
- result,
- value: result[valueKey],
- label: result[labelKey]
- }
- };
- })
- };
- });
- }}
- />
- );
+ return ;
}
return ;
};
diff --git a/geonode_mapstore_client/client/themes/geonode/less/_metadata.less b/geonode_mapstore_client/client/themes/geonode/less/_metadata.less
index 5004821196..91bb9c58c7 100644
--- a/geonode_mapstore_client/client/themes/geonode/less/_metadata.less
+++ b/geonode_mapstore_client/client/themes/geonode/less/_metadata.less
@@ -159,6 +159,7 @@
padding: 0.75rem;
border: 1px solid transparent;
border-radius: 8px;
+ margin: 0.75rem;
}
legend {
font-weight: bold;