在頁面中,當填寫地址信息時,需要動態展示下拉的相關地址信息,當選中某一地址信息,右邊則展示地圖,即相應的位置。前端使用的是React 以及Yarn
在前端需要添加插件: yarn add react-places-autocomplete(npm install react-places-autocomplete)
在你的頁面中導入Location.jsx組件
Location.jsx
import React from 'react';
import { Card, Checkbox, Col, Row, Form, Input } from 'antd';
import loadable from 'loadable-components';
import has from 'has';
import { geocodeByAddress, getLatLng } from 'react-places-autocomplete';
import PropTypes from 'prop-types';
import { FIELD_MAX_LENGTH } from 'App/Jobs/PostJob/Constants';
import './Location.scss';
const GoogleMap = loadable(() => import(/* webpackChunkName: "GoogleMap" */'App/Jobs/PostJob/Components/JobInfo/GoogleMap'));
const LocationSearchInput = loadable(() => import(/* webpackChunkName: "LocationSearchInput" */'App/Jobs/PostJob/Components/JobInfo/LocationSearchInput'));
class Location extends React.PureComponent {
state = {
showApplicant: has(this.props.jobDetail, 'showApplicant') ? this.props.jobDetail.showApplicant : false,
remote: has(this.props.jobDetail, 'remote') ? this.props.jobDetail.remote : false,
longitude: this.props.jobDetail.longitude || 0,
latitude: this.props.jobDetail.latitude || 0,
mapReady: false,
};
handleCheckShowApplicant = (e) => {
const { form: { setFieldsValue } } = this.props;
const showApplicant = e.target.checked;
this.setState({ showApplicant }, setFieldsValue({ showApplicant }));
}
handleCheckRemote = (e) => {
const { form: { setFieldsValue } } = this.props;
const remote = e.target.checked;
this.setState({ remote }, setFieldsValue({ remote }));
}
handleGetLocation = () => {
const { form: { getFieldsValue } } = this.props;
let location = '';
const {
streetAddress, city, state, zipCode,
} = getFieldsValue();
if (city && state) {
location = `${streetAddress}, ${city}, ${state}, ${zipCode}`;
location = location.replace(/^, |, $/g, '');
}
return location;
}
handleChange = () => {
const { form: { setFieldsValue } } = this.props;
const location = this.handleGetLocation();
setFieldsValue({ location });
if (location === '') {
return;
}
if (this.state.mapReady) {
geocodeByAddress(location)
.then(results => getLatLng(results[0]))
.then((latLng) => {
this.setState({
longitude: latLng.lng,
latitude: latLng.lat,
});
setFieldsValue({
longitude: latLng.lng,
latitude: latLng.lat,
});
}).catch(() => false);
}
}
handleMapReady = () => this.setState({ mapReady: true })
render() {
const {
form, jobDetail, locationIntro, handleWhichTipShow,
} = this.props;
const { getFieldDecorator } = form;
const {
showApplicant, longitude, latitude, remote, mapReady,
} = this.state;
return (
<Row>
<Col span={15}>
<div className="postjob-location">
<div className="title">Job location</div>
<Form.Item label="Street Address">
<LocationSearchInput
streetAddress={jobDetail.streetAddress || ''}
handlelocation={this.handleChange}
form={form}
handleAskButtonClick={() => handleWhichTipShow(false, false, !locationIntro)}
handleFocus={() => handleWhichTipShow(false, false, false)}
handleMouseOver={() => handleWhichTipShow(false, false, true)}
handleMouseOut={() => handleWhichTipShow(false, false, false)}
locationIntro={locationIntro}
mapReady={mapReady}
/>
</Form.Item>
<div className="location-style">
<Form.Item label="City" className="address-inline">
{getFieldDecorator('city', {
initialValue: jobDetail.city || '',
rules: [{ required: true, message: 'City is required.', whitespace: true }],
validateTrigger: ['onChange'],
})(<Input
size="large"
placeholder="City"
onFocus={() => handleWhichTipShow(false, false, false)}
onMouseOver={() => handleWhichTipShow(false, false, true)}
onMouseOut={() => handleWhichTipShow(false, false, false)}
onBlur={() => handleWhichTipShow(false, false, false)}
onKeyUp={this.handleChange}
maxLength={FIELD_MAX_LENGTH.LOCATION}
/>)}
</Form.Item>
<Form.Item label="State" className="address-inline">
{getFieldDecorator('state', {
initialValue: jobDetail.state || '',
rules: [{ required: true, message: 'State is required.', whitespace: true }],
validateTrigger: ['onChange'],
})(<Input
size="large"
placeholder="State"
onFocus={() => handleWhichTipShow(false, false, false)}
onMouseOver={() => handleWhichTipShow(false, false, true)}
onMouseOut={() => handleWhichTipShow(false, false, false)}
onBlur={() => handleWhichTipShow(false, false, false)}
onKeyUp={this.handleChange}
maxLength={FIELD_MAX_LENGTH.LOCATION}
/>)}
</Form.Item>
<Form.Item label="ZipCode" className="address-inline">
{getFieldDecorator('zipCode', {
initialValue: jobDetail.zipCode || '',
})(<Input
size="large"
placeholder="ZipCode"
onFocus={() => handleWhichTipShow(false, false, false)}
onMouseOver={() => handleWhichTipShow(false, false, true)}
onMouseOut={() => handleWhichTipShow(false, false, false)}
onBlur={() => handleWhichTipShow(false, false, false)}
onKeyUp={this.handleChange}
maxLength={FIELD_MAX_LENGTH.LOCATION}
/>)}
</Form.Item>
</div>
</div>
<Form.Item className="checkbox-position checkbox-first">
<Checkbox checked={remote} onChange={this.handleCheckRemote} disabled={showApplicant}>
This is a Telecommute/Work from Home position
</Checkbox>
</Form.Item>
<Form.Item className="checkbox-position">
<Checkbox
checked={remote ? false : showApplicant}
onChange={this.handleCheckShowApplicant}
disabled={remote}
>
Only show me applicants within 100 miles of this location
</Checkbox>
</Form.Item>
</Col>
<Col span={9}>
<Card className="google-map-layer">
{locationIntro &&
<p>Location is used to find job seekers near you.
You can hide your address, but city and state will still be visible.
</p>
}
<GoogleMap
form={form}
longitude={longitude}
latitude={latitude}
handleMapReady={this.handleMapReady}
/>
</Card>
</Col>
<div className="postjob-register-hidden-form">
<Form.Item>
{getFieldDecorator('latitude', { initialValue: jobDetail.latitude })(<div />)}
</Form.Item>
<Form.Item>
{getFieldDecorator('longitude', { initialValue: jobDetail.longitude })(<div />)}
</Form.Item>
<Form.Item>
{getFieldDecorator('remote', { initialValue: jobDetail.remote })(<div />)}
</Form.Item>
<Form.Item>
{getFieldDecorator('showApplicant', { initialValue: jobDetail.showApplicant })(<div />)}
</Form.Item>
<Form.Item>
{getFieldDecorator('location', { initialValue: jobDetail.location })(<div />)}
</Form.Item>
</div>
</Row>
);
}
}
Location.propTypes = {
form: PropTypes.shape().isRequired,
locationIntro: PropTypes.bool.isRequired,
handleWhichTipShow: PropTypes.func.isRequired,
jobDetail: PropTypes.shape().isRequired,
};
export default Location;
GoogleMap.jsx
import React from 'react';
import loadJs from 'loadjs';
import loadGoogleMap from 'App/Common/Utils/LoadGoogleMap';
import PropTypes from 'prop-types';
import isNumber from 'App/Common/Utils/CheckVariableType';
let marker;
let map;
class GoogleMap extends React.Component {
state = { isMapInit: false };
componentDidMount() {
loadGoogleMap();
const { form } = this.props;
const lat = form.getFieldValue('latitude');
const lng = form.getFieldValue('longitude');
this.initGoogleMap(lat, lng);
}
componentWillReceiveProps(nextProps) {
const { latitude, longitude } = nextProps;
const { isMapInit } = this.state;
if (this.props.latitude !== latitude || this.props.longitude !== longitude) {
if (!isMapInit) {
this.initGoogleMap(latitude, longitude);
} else {
if (marker) {
marker.setMap(null);
}
map.setCenter({ lat: latitude, lng: longitude });
marker = new window.google.maps.Marker({
position: new window.google.maps.LatLng(latitude, longitude),
map,
});
}
}
}
setupMapDom = (element) => {
this.mapRef = element;
}
initGoogleMap = (lat, lng) => {
loadJs.ready('googleMap', () => {
this.props.handleMapReady();
if (isNumber(lat) && isNumber(lng)) {
map = new window.google.maps.Map(this.mapRef, {
zoom: 15,
mapTypeControl: false,
zoomControl: true,
streetViewControl: false,
fullscreenControl: false,
});
map.setCenter({ lat, lng });
marker = new window.google.maps.Marker({
position: new window.google.maps.LatLng(lat, lng),
map,
});
this.addMapMarkerListener();
this.setState({ isMapInit: true });
}
});
}
addMapMarkerListener = () => {
map.addListener('click', (e) => {
const { form } = this.props;
if (marker) {
marker.setMap(null);
}
marker = new window.google.maps.Marker({
position: new window.google.maps.LatLng(e.latLng.lat(), e.latLng.lng()),
map,
});
form.setFieldsValue({
latitude: e.latLng.lat(),
longitude: e.latLng.lng(),
});
});
}
render() {
return (
<div
ref={(element) => {
this.setupMapDom(element);
}}
id="map"
/>
);
}
}
GoogleMap.propTypes = {
form: PropTypes.shape().isRequired,
latitude: PropTypes.number.isRequired,
longitude: PropTypes.number.isRequired,
handleMapReady: PropTypes.func.isRequired,
};
export default GoogleMap;
LocationSearchInput.jsx
import React from 'react';
import PlacesAutocomplete, { geocodeByAddress } from 'react-places-autocomplete';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Input, Button } from 'antd';
import notification from 'App/Common/Utils/Notification';
import { FIELD_MAX_LENGTH } from 'App/Jobs/PostJob/Constants';
import './LocationSearchInput.scss';
class LocationSearchInput extends React.Component {
state = {
streetAddress: this.props.streetAddress,
isFetchSuggestions: false,
};
componentWillReceiveProps(nextProps) {
const { mapReady, form } = nextProps;
if (mapReady) {
this.setState({ streetAddress: form.getFieldValue('streetAddress') || '' });
}
}
onLocationFocus =() => {
this.props.handleFocus();
this.setState({ isFetchSuggestions: true });
}
onLocationBlur = () => {
this.setState({ isFetchSuggestions: false });
}
onError = (status, clearSuggestions) => {
// eslint-disable-next-line no-console
console.warn('Google Maps API returned error with status: ', status);
clearSuggestions();
}
handleSelect = (address) => {
const { handlelocation, form } = this.props;
let city = '';
let state = '';
let zipCode = '';
let route = '';
let streetNumber = '';
geocodeByAddress(address)
.then((results) => {
results[0].address_components.forEach((value) => {
switch (value.types[0]) {
case 'administrative_area_level_1': state = value.long_name;
break;
case 'locality': city = value.long_name;
break;
case 'postal_code': zipCode = value.long_name;
break;
case 'route': route = value.long_name;
break;
case 'street_number': streetNumber = value.long_name;
break;
default: break;
}
});
const streetAddress = `${streetNumber} ${route}`.replace(/^ /, '') || '';
this.setState({ streetAddress });
form.setFieldsValue({
streetAddress,
city,
state,
zipCode,
}, handlelocation);
}).catch(err => notification('err', 'Error', err));
};
handleChange = (streetAddress) => {
const { handlelocation } = this.props;
this.setState({ streetAddress });
this.props.form.setFieldsValue({
streetAddress,
}, handlelocation);
};
render() {
const { isFetchSuggestions, streetAddress } = this.state;
const {
mapReady, handleAskButtonClick, handleMouseOver, handleMouseOut,
locationIntro, form, handleFocus,
} = this.props;
return (
<div className="location_input">
{!mapReady ?
<div className="street-address-for-google-failed-div">
{form.getFieldDecorator('streetAddress', {
initialValue: streetAddress || '',
})(<Input
className="street-address-for-google-failed"
onFocus={handleFocus}
onBlur={handleMouseOut}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
maxLength={FIELD_MAX_LENGTH.LOCATION}
/>)}
</div> :
<PlacesAutocomplete
value={this.state.streetAddress}
onChange={this.handleChange}
onSelect={this.handleSelect}
shouldFetchSuggestions={isFetchSuggestions}
highlightFirstSuggestion
onError={this.onError}
>
{({
getInputProps, suggestions, getSuggestionItemProps, loading,
}) => (
<div className="autocomplete-place-input">
<Input
size="large"
{...getInputProps({
className: 'location-search-input',
})}
className="location-box"
onBlur={this.onLocationBlur}
value={streetAddress}
onFocus={this.onLocationFocus}
onMouseOver={handleMouseOver}
onMouseOut={() => handleMouseOut()}
maxLength={FIELD_MAX_LENGTH.LOCATION}
/>
<div hidden={!isFetchSuggestions || suggestions.length === 0} className={suggestions.length === 0 ? 'autocomplete-dropdown-container' : 'autocomplete-dropdown-container autocomplete-dropdown-border'}>
{loading && <div>Loading...</div>}
{suggestions.map((suggestion) => {
const className = suggestion.active
? 'suggestion-item--active'
: 'suggestion-item';
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: '#fafafa', cursor: 'pointer' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
{form.getFieldDecorator('streetAddress', {
initialValue: streetAddress || '',
})(<Input hidden />)}
</div>
)}
</PlacesAutocomplete>}
<Button className={classnames('ask-button', 'location-ask-button', { selected: locationIntro })} shape="circle" onClick={handleAskButtonClick}>?</Button>
</div>
);
}
}
LocationSearchInput.propTypes = {
form: PropTypes.shape({
getFieldValue: PropTypes.func.isRequired,
getFieldDecorator: PropTypes.func.isRequired,
setFieldsValue: PropTypes.func.isRequired,
}).isRequired,
handlelocation: PropTypes.func.isRequired,
handleAskButtonClick: PropTypes.func.isRequired,
handleFocus: PropTypes.func.isRequired,
handleMouseOver: PropTypes.func.isRequired,
handleMouseOut: PropTypes.func.isRequired,
locationIntro: PropTypes.bool.isRequired,
streetAddress: PropTypes.string.isRequired,
};
export default LocationSearchInput;
LoadGoogleMap.js
該文件需要GOOGLE_MAP_KEY的值
import loadJs from 'loadjs';
export default function loadGoogleMap() {
if (!loadJs.isDefined('googleMap') && typeof GOOGLE_MAP_KEY === 'string') {
loadJs(`https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAP_KEY}&libraries=places`, 'googleMap');
}
}