mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-27 19:42:11 +10:00
Merge pull request #894 from sharetribe/autocomplete-default-searches
Default location suggestions for LocationAutocompleteInput
This commit is contained in:
commit
fd4c39d01d
5 changed files with 115 additions and 25 deletions
|
|
@ -13,6 +13,12 @@ way to update this template, but currently, we follow a pattern:
|
|||
---
|
||||
## Upcoming version 2018-08-XX
|
||||
|
||||
* [change] Add support for default locations in the
|
||||
LocationAutocompleteInput component. Common searches can be
|
||||
configured to show when the input has focus. This reduces typing and
|
||||
Google Places geolocation API usage. The defaults can be configured
|
||||
in `src/components/LocationAutocompleteInput/GeocoderGoogleMaps.js`.
|
||||
[#894](https://github.com/sharetribe/flex-template-web/pull/894)
|
||||
* [change] Removed the `country` parameter from the search page as it
|
||||
was not used anywhere.
|
||||
[#893](https://github.com/sharetribe/flex-template-web/pull/893)
|
||||
|
|
|
|||
|
|
@ -25,3 +25,14 @@ local development, you can add the variable in the Gitignored `.env` file in the
|
|||
```
|
||||
REACT_APP_GOOGLE_MAPS_API_KEY=my-key-here
|
||||
```
|
||||
|
||||
## Setup common locations to reduce typing
|
||||
|
||||
The location autocomplete input in the landing page and the topbar can
|
||||
be configured to have specific locations shown by default when the
|
||||
user focuses on the input and hasn't yet typed in any searches. This
|
||||
reduces the typing required for common searches, and also reduces the
|
||||
need to use the Google Places geolocation API that much.
|
||||
|
||||
The default locations can be configured in
|
||||
[src/components/LocationAutocompleteInput/GeocoderGoogleMaps.js](../src/components/LocationAutocompleteInput/GeocoderGoogleMaps.js).
|
||||
|
|
|
|||
|
|
@ -3,6 +3,30 @@ import { getPlacePredictions, getPlaceDetails } from '../../util/googleMaps';
|
|||
|
||||
import css from './LocationAutocompleteInput.css';
|
||||
|
||||
// A list of default predictions that can be shown when the user
|
||||
// focuses on the autocomplete input without typing a search. This can
|
||||
// be used to reduce typing and Geocoding API calls for common
|
||||
// searches.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// place_id: 'Place ID from Google Maps Places API',
|
||||
// description: 'Place name to show in the autocomplete dropdown',
|
||||
// },
|
||||
// ]
|
||||
//
|
||||
// To know which values to set as defaults, log a real prediction
|
||||
// object from the Places API call and copy the Place ID from the
|
||||
// response.
|
||||
export const defaultPredictions = [
|
||||
// {
|
||||
// place_id: 'ChIJkQYhlscLkkYRY_fiO4S9Ts0',
|
||||
// description: 'Helsinki, Finland',
|
||||
// },
|
||||
];
|
||||
|
||||
// When displaying data from the Google Maps Places API, and
|
||||
// attribution is required next to the results.
|
||||
// See: https://developers.google.com/places/web-service/policies#powered
|
||||
|
|
@ -44,6 +68,13 @@ class GeocoderGoogleMaps {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the given prediction.
|
||||
*/
|
||||
getPredictionId(prediction) {
|
||||
return prediction.place_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the address text of the given prediction.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import config from '../../config';
|
|||
|
||||
const { LatLng: SDKLatLng, LatLngBounds: SDKLatLngBounds } = sdkTypes;
|
||||
|
||||
const placeId = prediction => prediction.id;
|
||||
|
||||
const placeAddress = prediction => prediction.place_name;
|
||||
|
||||
const placeOrigin = prediction => {
|
||||
|
|
@ -24,6 +26,34 @@ const placeBounds = prediction => {
|
|||
return null;
|
||||
};
|
||||
|
||||
// A list of default predictions that can be shown when the user
|
||||
// focuses on the autocomplete input without typing a search. This can
|
||||
// be used to reduce typing and Geocoding API calls for common
|
||||
// searches.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// id: 'any unique id',
|
||||
// place_name: 'Place name to show in the autocomplete dropdown',
|
||||
// center: [longitude, latitude],
|
||||
// bbox: [minX, minY, maxX, maxY],
|
||||
// },
|
||||
// ]
|
||||
//
|
||||
// To know which values to set as defaults, log a real prediction
|
||||
// object from the Geocoding API call and check the values from the
|
||||
// response.
|
||||
export const defaultPredictions = [
|
||||
// {
|
||||
// id: 'default-helsinki',
|
||||
// place_name: 'Helsinki, Finland',
|
||||
// center: [24.94861, 60.17333],
|
||||
// bbox: [24.82617, 60.075361, 25.313112, 60.297839],
|
||||
// },
|
||||
];
|
||||
|
||||
export const GeocoderAttribution = () => null;
|
||||
|
||||
/**
|
||||
|
|
@ -66,6 +96,13 @@ class GeocoderMapbox {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the given prediction.
|
||||
*/
|
||||
getPredictionId(prediction) {
|
||||
return placeId(prediction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the address text of the given prediction.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { any, arrayOf, bool, func, number, shape, string, oneOfType, object } fr
|
|||
import classNames from 'classnames';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { propTypes } from '../../util/types';
|
||||
import Geocoder, { GeocoderAttribution } from './GeocoderGoogleMaps';
|
||||
import Geocoder, { GeocoderAttribution, defaultPredictions } from './GeocoderGoogleMaps';
|
||||
// import Geocoder, { GeocoderAttribution, defaultPredictions } from './GeocoderMapbox';
|
||||
|
||||
import css from './LocationAutocompleteInput.css';
|
||||
|
||||
|
|
@ -69,12 +70,12 @@ const LocationPredictionsList = props => {
|
|||
return (
|
||||
<li
|
||||
className={isHighlighted ? css.highlighted : null}
|
||||
key={prediction.id}
|
||||
key={geocoder.getPredictionId(prediction)}
|
||||
onTouchStart={e => onSelectStart(getTouchCoordinates(e.nativeEvent))}
|
||||
onMouseDown={() => onSelectStart()}
|
||||
onTouchMove={e => onSelectMove(getTouchCoordinates(e.nativeEvent))}
|
||||
onTouchEnd={() => onSelectEnd(index)}
|
||||
onMouseUp={() => onSelectEnd(index)}
|
||||
onTouchEnd={() => onSelectEnd(prediction)}
|
||||
onMouseUp={() => onSelectEnd(prediction)}
|
||||
>
|
||||
{geocoder.getPredictionAddress(prediction)}
|
||||
</li>
|
||||
|
|
@ -148,8 +149,9 @@ class LocationAutocompleteInputImpl extends Component {
|
|||
|
||||
this.geocoder = new Geocoder();
|
||||
|
||||
this.currentPredictions = this.currentPredictions.bind(this);
|
||||
this.changeHighlight = this.changeHighlight.bind(this);
|
||||
this.selectItem = this.selectItem.bind(this);
|
||||
this.selectPrediction = this.selectPrediction.bind(this);
|
||||
this.selectItemIfNoneSelected = this.selectItemIfNoneSelected.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
|
|
@ -164,6 +166,13 @@ class LocationAutocompleteInputImpl extends Component {
|
|||
this.predict = debounce(this.predict.bind(this), DEBOUNCE_WAIT_TIME, { leading: true });
|
||||
}
|
||||
|
||||
currentPredictions() {
|
||||
const { search, predictions: fetchedPredictions } = currentValue(this.props);
|
||||
const hasFetchedPredictions = fetchedPredictions && fetchedPredictions.length > 0;
|
||||
|
||||
return !search && !hasFetchedPredictions ? defaultPredictions : fetchedPredictions;
|
||||
}
|
||||
|
||||
// Interpret input key event
|
||||
onKeyDown(e) {
|
||||
if (e.keyCode === KEY_CODE_ARROW_UP) {
|
||||
|
|
@ -175,9 +184,9 @@ class LocationAutocompleteInputImpl extends Component {
|
|||
e.preventDefault();
|
||||
this.changeHighlight(DIRECTION_DOWN);
|
||||
} else if (e.keyCode === KEY_CODE_ENTER) {
|
||||
const { search, selectedPlace } = currentValue(this.props);
|
||||
const { selectedPlace } = currentValue(this.props);
|
||||
|
||||
if (search && !selectedPlace) {
|
||||
if (!selectedPlace) {
|
||||
// Prevent form submit, try to select value instead.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
@ -193,7 +202,7 @@ class LocationAutocompleteInputImpl extends Component {
|
|||
// Handle input text change, fetch predictions if the value isn't empty
|
||||
onChange(e) {
|
||||
const onChange = this.props.input.onChange;
|
||||
const { predictions } = currentValue(this.props);
|
||||
const predictions = this.currentPredictions();
|
||||
const newValue = e.target.value;
|
||||
|
||||
// Clear the current values since the input content is changed
|
||||
|
|
@ -220,7 +229,7 @@ class LocationAutocompleteInputImpl extends Component {
|
|||
// (DIRECTION_UP or DIRECTION_DOWN)
|
||||
changeHighlight(direction) {
|
||||
this.setState((prevState, props) => {
|
||||
const { predictions } = currentValue(props);
|
||||
const predictions = this.currentPredictions();
|
||||
const currentIndex = prevState.highlightedIndex;
|
||||
let index = currentIndex;
|
||||
|
||||
|
|
@ -244,16 +253,7 @@ class LocationAutocompleteInputImpl extends Component {
|
|||
|
||||
// Select the prediction in the given item. This will fetch/read the
|
||||
// place details and set it as the selected place.
|
||||
selectItem(index) {
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
const { predictions } = currentValue(this.props);
|
||||
if (index >= predictions.length) {
|
||||
return;
|
||||
}
|
||||
const prediction = predictions[index];
|
||||
|
||||
selectPrediction(prediction) {
|
||||
this.props.input.onChange({
|
||||
...this.props.input,
|
||||
selectedPlace: null,
|
||||
|
|
@ -278,10 +278,14 @@ class LocationAutocompleteInputImpl extends Component {
|
|||
});
|
||||
}
|
||||
selectItemIfNoneSelected() {
|
||||
const { search, selectedPlace } = currentValue(this.props);
|
||||
if (search && !selectedPlace) {
|
||||
const { selectedPlace } = currentValue(this.props);
|
||||
const predictions = this.currentPredictions();
|
||||
if (!selectedPlace) {
|
||||
const index = this.state.highlightedIndex !== -1 ? this.state.highlightedIndex : 0;
|
||||
this.selectItem(index);
|
||||
|
||||
if (index >= 0 && index < predictions.length) {
|
||||
this.selectPrediction(predictions[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
predict(search) {
|
||||
|
|
@ -350,7 +354,7 @@ class LocationAutocompleteInputImpl extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
handlePredictionsSelectEnd(index) {
|
||||
handlePredictionsSelectEnd(prediction) {
|
||||
let selectAndFinalize = false;
|
||||
this.setState(
|
||||
prevState => {
|
||||
|
|
@ -361,7 +365,7 @@ class LocationAutocompleteInputImpl extends Component {
|
|||
},
|
||||
() => {
|
||||
if (selectAndFinalize) {
|
||||
this.selectItem(index);
|
||||
this.selectPrediction(prediction);
|
||||
this.finalizeSelection();
|
||||
}
|
||||
}
|
||||
|
|
@ -383,9 +387,10 @@ class LocationAutocompleteInputImpl extends Component {
|
|||
inputRef,
|
||||
} = this.props;
|
||||
const { name, onFocus } = input;
|
||||
const { search, predictions } = currentValue(this.props);
|
||||
const { search } = currentValue(this.props);
|
||||
const { touched, valid } = meta || {};
|
||||
const isValid = valid && touched;
|
||||
const predictions = this.currentPredictions();
|
||||
|
||||
const handleOnFocus = e => {
|
||||
this.setState({ inputHasFocus: true });
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue