React google map

在頁面中,當填寫地址信息時,需要動態展示下拉的相關地址信息,當選中某一地址信息,右邊則展示地圖,即相應的位置。前端使用的是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>}
        &nbsp;&nbsp;&nbsp;
        <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');
  }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章