diff --git a/src/Field/WfsSearch/WfsSearch.example.md b/src/Field/WfsSearch/WfsSearch.example.md deleted file mode 100644 index 1c76e7909f..0000000000 --- a/src/Field/WfsSearch/WfsSearch.example.md +++ /dev/null @@ -1,72 +0,0 @@ -This demonstrates the usage of the WfsSearch. -Type a country name in its own language… - -```jsx -import WfsSearch from '@terrestris/react-geo/dist/Field/WfsSearch/WfsSearch'; -import MapComponent from '@terrestris/react-util/dist/Components/MapComponent/MapComponent'; -import MapContext from '@terrestris/react-util/dist/Context/MapContext/MapContext'; -import OlLayerTile from 'ol/layer/Tile'; -import OlMap from 'ol/Map'; -import { fromLonLat } from 'ol/proj'; -import OlSourceOSM from 'ol/source/OSM'; -import OlView from 'ol/View'; -import {useEffect, useState} from 'react'; - -const WfsSearchExample = () => { - - const [map, setMap] = useState(); - - useEffect(() => { - const newMap = new OlMap({ - layers: [ - new OlLayerTile({ - name: 'OSM', - source: new OlSourceOSM() - }) - ], - view: new OlView({ - center: fromLonLat([37.40570, 8.81566]), - zoom: 4 - }) - }); - - setMap(newMap); - }, []); - - if (!map) { - return null; - } - - return ( - -
- -
- -
- ); -}; - -``` diff --git a/src/Field/WfsSearch/WfsSearch.spec.tsx b/src/Field/WfsSearch/WfsSearch.spec.tsx deleted file mode 100644 index 67b80126f5..0000000000 --- a/src/Field/WfsSearch/WfsSearch.spec.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import Logger from '@terrestris/base-util/dist/Logger'; -import {OptionProps} from 'antd/lib/select'; -import OlLayerTile from 'ol/layer/Tile'; -import OlMap from 'ol/Map'; -import OlSourceOsm from 'ol/source/OSM'; -import OlView from 'ol/View'; -import { - act -} from 'react-dom/test-utils'; - -import TestUtil from '../../Util/TestUtil'; -import WfsSearch, {WfsSearchProps, WfsSearchState} from './WfsSearch'; - -describe('', () => { - it('is defined', () => { - expect(WfsSearch).not.toBeUndefined(); - }); - - it('can be rendered', () => { - const wrapper = TestUtil.mountComponent(WfsSearch); - expect(wrapper).not.toBeUndefined(); - }); - - describe('#onUpdateInput', () => { - - it('sets the inputValue as state.searchTerm', () => { - const wrapper = TestUtil.mountComponent(WfsSearch); - const instance = wrapper.instance() as WfsSearch; - - const inputValue = 'a'; - act(() => { - instance.onUpdateInput(inputValue); - }); - let state = wrapper.state() as WfsSearchState; - expect(state.searchTerm).toBe(inputValue); - }); - - it('sends a request if input is as long as props.minChars', () => { - // expect.assertions(1); - const wrapper = TestUtil.mountComponent(WfsSearch, { - placeholder: 'Type a countryname in its own language…', - baseUrl: 'https://ows-demo.terrestris.de/geoserver/osm/wfs', - featureTypes: ['osm:osm-country-borders'], - attributeDetails: { - 'osm:osm-country-borders': { - name: { - type: 'string', - exactSearch: false, - matchCase: false - } - } - } - }); - const instance = wrapper.instance() as WfsSearch; - const doSearchSpy = jest.spyOn(instance, 'doSearch'); - const inputValue = 'Deutsch'; - act(() => { - instance.onUpdateInput(inputValue); - }); - expect(doSearchSpy).toHaveBeenCalled(); - doSearchSpy.mockRestore(); - }); - }); - - describe('#onFetchSuccess', () => { - it('sets the response features as state.data', () => { - const wrapper = TestUtil.mountComponent(WfsSearch); - const response = { - features: [{ - id: '752526', - properties: { - name: 'Deutschland' - } - }] - }; - const instance = wrapper.instance() as WfsSearch; - instance.onFetchSuccess(response); - const promise = new Promise(resolve => { - setTimeout(resolve, 350); - }); - return promise.then(() => { - expect((wrapper.state() as WfsSearchState).data).toEqual(response.features); - }); - }); - }); - - describe('#onFetchError', () => { - it('sets the response as state.data', () => { - const wrapper = TestUtil.mountComponent(WfsSearch); - const loggerSpy = jest.spyOn(Logger, 'error'); - const instance = wrapper.instance() as WfsSearch; - instance.onFetchError('Peter'); - expect(loggerSpy).toHaveBeenCalled(); - expect(loggerSpy).toHaveBeenCalledWith('Error while requesting WFS GetFeature: Peter'); - loggerSpy.mockRestore(); - }); - }); - - describe('#onMenuItemSelected', () => { - it('calls this.props.onSelect with the selected item', () => { - // SETUP - const data = [{ - id: '752526', - properties: { - name: 'Deutschland' - } - }]; - const map = new OlMap({ - layers: [new OlLayerTile({ source: new OlSourceOsm(), properties: {name: 'OSM'} })], - view: new OlView({ - projection: 'EPSG:4326', - center: [37.40570, 8.81566], - zoom: 4 - }) - }); - // SETUP END - - const selectSpy = jest.fn(); - const wrapper = TestUtil.mountComponent(WfsSearch, { - onSelect: selectSpy, - map - }); - const instance = wrapper.instance() as WfsSearch; - act(() => { - wrapper.setState({ - data: data - }); - }); - act(() => { - const op: OptionProps = { key: '752526', children: null }; - instance.onMenuItemSelected('Deutschland', op); - }); - expect(selectSpy).toHaveBeenCalled(); - expect(selectSpy).toHaveBeenCalledWith(data[0], map); - - selectSpy.mockRestore(); - }); - }); - - describe('default #onSelect', () => { - it('zooms to the selected feature', () => { - // SETUP - const feature = { - type: 'Feature', - id: '752526', - properties: { - name: 'Peter', - }, - geometry: { - type: 'Polygon', - coordinates: [[[10, 40], [40, 40], [40, 10], [10, 10], [10, 40]]] - } - }; - const map = new OlMap({ - layers: [new OlLayerTile({ source: new OlSourceOsm(), properties: {name: 'OSM'} })], - view: new OlView({ - projection: 'EPSG:4326', - center: [37.40570, 8.81566], - zoom: 4, - constrainResolution: true - }) - }); - // SETUP END - - const wrapper = TestUtil.mountComponent(WfsSearch, { map }); - const fitSpy = jest.spyOn(map.getView(), 'fit'); - (wrapper.props() as WfsSearchProps).onSelect(feature, map); - - expect.assertions(3); - - expect(fitSpy).toHaveBeenCalled(); - - return new Promise(resolve => { - setTimeout(resolve, 600); - }) - .then(() => { - expect(map.getView().getCenter()).toEqual([25, 25]); - expect(map.getView().getZoom()).toEqual(2); - fitSpy.mockRestore(); - }); - }); - }); - - describe('#renderOption', () => { - it('returns a Select.Option', () => { - const wrapper = TestUtil.mountComponent(WfsSearch); - const feature = { - id: '752526', - properties: { - name: 'Deutschland' - } - }; - const option = (wrapper.props() as WfsSearchProps).renderOption(feature, { - // Props must be passed to the renderOption function. - displayValue: 'name', - idProperty: 'id' - }); - - expect(option.key).toBe(feature.id); - expect(option.props.children).toBe(feature.properties.name); - }); - }); - - describe('#idProperty', () => { - it('can be specified', () => { - const wrapper = TestUtil.mountComponent(WfsSearch); - const feature = { - customId: '7355608', - properties: { - name: 'Deutschland' - } - }; - const option = (wrapper.props() as WfsSearchProps).renderOption(feature, { - displayValue: 'name', - idProperty: 'customId' - }); - - expect(option.key).toBe(feature.customId); - expect(option.props.children).toBe(feature.properties.name); - }); - }); - -}); diff --git a/src/Field/WfsSearchField/WfsSearchField.example.md b/src/Field/WfsSearchField/WfsSearchField.example.md new file mode 100644 index 0000000000..89efed03a7 --- /dev/null +++ b/src/Field/WfsSearchField/WfsSearchField.example.md @@ -0,0 +1,121 @@ +This demonstrates the usage of the WfsSearch. +Type a country name in its own language… + +```jsx +import WfsSearch from '@terrestris/react-geo/dist/Field/WfsSearchField/WfsSearchField'; +import MapComponent from '@terrestris/react-util/dist/Components/MapComponent/MapComponent'; +import MapContext from '@terrestris/react-util/dist/Context/MapContext/MapContext'; +import AgFeatureGrid from '@terrestris/react-geo/dist/Grid/AgFeatureGrid/AgFeatureGrid'; +import OlLayerTile from 'ol/layer/Tile'; +import OlMap from 'ol/Map'; +import { fromLonLat } from 'ol/proj'; +import OlSourceOSM from 'ol/source/OSM'; +import OlView from 'ol/View'; +import {useEffect, useState} from 'react'; + +const WfsSearchFieldExample = () => { + + const [map, setMap] = useState(); + const [inputFeatures, setInputFeatures] = useState([]); + + useEffect(() => { + const newMap = new OlMap({ + layers: [ + new OlLayerTile({ + name: 'OSM', + source: new OlSourceOSM() + }) + ], + view: new OlView({ + center: fromLonLat([37.40570, 8.81566]), + zoom: 4 + }) + }); + + setMap(newMap); + }, []); + + if (!map) { + return null; + } + + const onFeaturesChange = f => setInputFeatures(f); + + return ( + +
+ +
+
+ +
+ +
+ ); +}; + +``` diff --git a/src/Field/WfsSearch/WfsSearch.less b/src/Field/WfsSearchField/WfsSearchField.less similarity index 100% rename from src/Field/WfsSearch/WfsSearch.less rename to src/Field/WfsSearchField/WfsSearchField.less diff --git a/src/Field/WfsSearchField/WfsSearchField.spec.tsx b/src/Field/WfsSearchField/WfsSearchField.spec.tsx new file mode 100644 index 0000000000..40143d5103 --- /dev/null +++ b/src/Field/WfsSearchField/WfsSearchField.spec.tsx @@ -0,0 +1,14 @@ +import TestUtil from '../../Util/TestUtil'; +import WfsSearchField from './WfsSearchField'; + +describe('', () => { + it('is defined', () => { + expect(WfsSearchField).not.toBeUndefined(); + }); + + it('can be rendered', () => { + const wrapper = TestUtil.mountComponent(WfsSearchField); + expect(wrapper).not.toBeUndefined(); + }); + +}); diff --git a/src/Field/WfsSearch/WfsSearch.tsx b/src/Field/WfsSearchField/WfsSearchField.tsx similarity index 61% rename from src/Field/WfsSearch/WfsSearch.tsx rename to src/Field/WfsSearchField/WfsSearchField.tsx index ca17688819..103f68b81f 100644 --- a/src/Field/WfsSearch/WfsSearch.tsx +++ b/src/Field/WfsSearchField/WfsSearchField.tsx @@ -1,51 +1,50 @@ -import './WfsSearch.less'; +import './WfsSearchField.less'; +import { faCircleNotch, faClose } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Logger from '@terrestris/base-util/dist/Logger'; import { SearchConfig } from '@terrestris/ol-util/dist/WfsFilterUtil/WfsFilterUtil'; import useMap from '@terrestris/react-util/dist/Hooks/useMap/useMap'; import { useWfs } from '@terrestris/react-util/dist/Hooks/useWfs/useWfs'; -import { AutoComplete, Spin } from 'antd'; -import { AutoCompleteProps } from 'antd/lib/auto-complete'; +import { AutoComplete, Input, Spin } from 'antd'; import { DefaultOptionType, OptionProps } from 'antd/lib/select'; import _get from 'lodash/get'; import _isNil from 'lodash/isNil'; +import _isString from 'lodash/isString'; import OlFeature from 'ol/Feature'; -import React, { FC, useMemo, useState } from 'react'; +import React, { FC, useEffect, useMemo, useState } from 'react'; import { CSS_PREFIX } from '../../constants'; const Option = AutoComplete.Option; -export interface WfsSearchState { - data: Array; - fetching: boolean; - searchTerm: string; -} - -export type WfsSearchProps = { +export type WfsSearchFieldProps = { additionalFetchOptions?: Partial; + asInput?: boolean; baseUrl: string; className?: string; displayValue?: string; idProperty?: string; minChars?: number; -} & SearchConfig & AutoCompleteProps ; + onBeforeSearch?: (value: string) => string; + onChange?: (val: OlFeature[] | undefined) => undefined; + value?: OlFeature[] | undefined; +} & SearchConfig; const defaultClassName = `${CSS_PREFIX}wfssearch`; /** - * The WfsSearch field. - * Implements an input field to do a WFS-GetFeature request. + * The WfsSearchField field. + * Implements a field to do a WFS-GetFeature request. * * The GetFeature request is created with `ol.format.WFS.writeGetFeature` * so most of the WFS specific options work like document in the corresponding * API-docs: https://openlayers.org/en/latest/apidoc/module-ol_format_WFS.html * - * @class WfsSearch - * @extends React.Component */ -export const WfsSearch: FC = ({ +export const WfsSearchField: FC = ({ additionalFetchOptions, + asInput = false, attributeDetails = {}, baseUrl, className, @@ -57,9 +56,12 @@ export const WfsSearch: FC = ({ idProperty = 'id', maxFeatures, minChars = 3, + onBeforeSearch = v => v, + onChange = () => undefined, outputFormat = 'application/json', propertyNames, srsName = 'EPSG:3857', + value, wfsFormatOptions, ...passThroughProps }) => { @@ -114,19 +116,19 @@ export const WfsSearch: FC = ({ }); /** - * Create an AutoComplete.Option from the given data. - * - * @param feature The feature as returned by the server. - * @return The AutoComplete.Option that will be - * rendered for each feature. - */ + * Create an AutoComplete.Option from the given data. + * + * @param feature The feature as returned by the server. + * @return The AutoComplete.Option that will be + * rendered for each feature. + */ const renderOption = (feature?: OlFeature): React.ReactElement => { if (_isNil(feature)) { return <>; } - const value = _get(feature?.getProperties(), displayValue); - const display = _isNil(value) ? feature.get(idProperty) : value; + const v = _get(feature?.getProperties(), displayValue); + const display = _isNil(v) ? feature.get(idProperty) : v; return (