Skip to content

Commit 4013071

Browse files
authored
Autocomplete: Single select entry and pagination support (#1903)
1 parent 78f7a50 commit 4013071

File tree

6 files changed

+201
-98
lines changed

6 files changed

+201
-98
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2024, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
import React from 'react';
10+
import isArray from 'lodash/isArray';
11+
import PropTypes from 'prop-types';
12+
13+
import SelectInfiniteScroll from '@js/components/SelectInfiniteScroll/SelectInfiniteScroll';
14+
15+
const Autocomplete = ({
16+
className,
17+
clearable = false,
18+
id,
19+
labelKey,
20+
multi = false,
21+
name,
22+
title,
23+
value,
24+
valueKey,
25+
placeholder,
26+
onChange,
27+
onLoadOptions,
28+
...props
29+
}) => {
30+
const getValue = () => {
31+
if (value && isArray(value) && valueKey && labelKey) {
32+
return value.map((entry) => {
33+
return {
34+
result: entry,
35+
value: entry[valueKey],
36+
label: entry[labelKey]
37+
};
38+
});
39+
}
40+
return value;
41+
};
42+
return (
43+
<div className={`autocomplete${className ? " " + className : ""}`}>
44+
<label className="control-label" htmlFor={id}>{title || name}</label>
45+
<SelectInfiniteScroll
46+
{...props}
47+
id={id}
48+
value={getValue()}
49+
multi={multi}
50+
clearable={clearable}
51+
placeholder={placeholder}
52+
loadOptions={onLoadOptions}
53+
onChange={onChange}
54+
/>
55+
</div>
56+
);
57+
};
58+
59+
Autocomplete.propTypes = {
60+
className: PropTypes.string,
61+
clearable: PropTypes.bool,
62+
id: PropTypes.string.isRequired,
63+
labelKey: PropTypes.string,
64+
multi: PropTypes.bool,
65+
name: PropTypes.string,
66+
title: PropTypes.string,
67+
value: PropTypes.any.isRequired,
68+
valueKey: PropTypes.string,
69+
placeholder: PropTypes.string,
70+
onChange: PropTypes.func.isRequired,
71+
onLoadOptions: PropTypes.func.isRequired
72+
};
73+
74+
export default Autocomplete;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright 2024, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
export { default } from './Autocomplete';

geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_fields/ArrayField.jsx

Lines changed: 0 additions & 96 deletions
This file was deleted.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2024, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
import React from 'react';
10+
import axios from '@mapstore/framework/libs/ajax';
11+
import isString from 'lodash/isString';
12+
import isEmpty from 'lodash/isEmpty';
13+
import Autocomplete from '@js/components/Autocomplete/Autocomplete';
14+
import DefaultSchemaField from '@rjsf/core/lib/components/fields/SchemaField';
15+
16+
/**
17+
* `SchemaField` is an enhanced component that overrides `@rjsf`'s default `SchemaField`
18+
* - Customizes the rendering of object and array fields with custom functionality, such as support for autocomplete fields.
19+
* - Provides `onChange` handlers to update `formData` dynamically.
20+
* - Fallback to the default `SchemaField` from `@rjsf` when no custom rendering is needed.
21+
*/
22+
const SchemaField = (props) => {
23+
const {
24+
onChange,
25+
schema,
26+
formData,
27+
idSchema,
28+
name,
29+
uiSchema
30+
} = props;
31+
const autocomplete = uiSchema?.['ui:options']?.['geonode-ui:autocomplete'];
32+
const isMultiSelect = schema?.items?.type === 'object' && !isEmpty(schema?.items?.properties);
33+
const isSingleSelect = schema?.type === 'object' && !isEmpty(schema?.properties);
34+
35+
if (autocomplete && (isMultiSelect || isSingleSelect)) {
36+
const autocompleteOptions = isString(autocomplete)
37+
? { url: autocomplete }
38+
: autocomplete;
39+
const autocompleteUrl = autocompleteOptions?.url;
40+
const queryKey = autocompleteOptions?.queryKey || 'q';
41+
const resultsKey = autocompleteOptions?.resultsKey || 'results';
42+
const valueKey = autocompleteOptions?.valueKey || 'id';
43+
const labelKey = autocompleteOptions?.labelKey || 'label';
44+
const placeholder = autocompleteOptions?.placeholder ?? '...';
45+
46+
let autoCompleteProps = {
47+
id: idSchema.$id,
48+
name,
49+
title: schema.title,
50+
value: formData,
51+
valueKey,
52+
labelKey,
53+
placeholder,
54+
multi: isMultiSelect,
55+
clearable: !isMultiSelect,
56+
onChange: (selected) => {
57+
let _selected = selected?.result ?? null;
58+
if (isMultiSelect) {
59+
_selected = selected.map(({ result, ...option }) => {
60+
if (result === undefined) {
61+
return option;
62+
}
63+
return Object.fromEntries(
64+
Object.keys(schema.items.properties)
65+
.map((key) => [key, result[key]])
66+
);
67+
});
68+
}
69+
onChange(_selected);
70+
}
71+
};
72+
73+
return (
74+
<Autocomplete
75+
{...autoCompleteProps}
76+
className={"form-group gn-metadata-autocomplete"}
77+
onLoadOptions={({ q, config, ...params }) => {
78+
return axios.get(autocompleteUrl, {
79+
...config,
80+
params: {
81+
...params,
82+
...(q && { [queryKey]: q }),
83+
page: params.page
84+
}
85+
})
86+
.then(({ data }) => {
87+
return {
88+
isNextPageAvailable: !!data.pagination?.more,
89+
results: data?.[resultsKey].map((result) => {
90+
return {
91+
selectOption: {
92+
result,
93+
value: result[valueKey],
94+
label: result[labelKey]
95+
}
96+
};
97+
})
98+
};
99+
});
100+
}}
101+
/>
102+
);
103+
}
104+
return <DefaultSchemaField {...props}/>;
105+
};
106+
107+
export default SchemaField;

geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_fields/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9-
import ArrayField from './ArrayField';
9+
import SchemaField from './SchemaField';
1010

1111
export default {
12-
ArrayField: ArrayField
12+
SchemaField
1313
};

geonode_mapstore_client/client/themes/geonode/less/_metadata.less

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,15 @@
194194
grid-column-start: 2;
195195
}
196196
}
197+
.gn-metadata-autocomplete {
198+
padding: 0 0.75rem;
199+
.Select--multi {
200+
.Select-value {
201+
margin-top: 3px;
202+
margin-bottom: 3px;
203+
}
204+
}
205+
}
197206
}
198207

199208
.gn-metadata-page {

0 commit comments

Comments
 (0)