UNPKG

react-geosuggest-mui

Version:

A React autosuggest for the Google Maps Places API.

553 lines (433 loc) 15.9 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _classnames = require('classnames'); var _classnames2 = _interopRequireDefault(_classnames); var _lodash = require('lodash.debounce'); var _lodash2 = _interopRequireDefault(_lodash); var _defaults = require('./defaults'); var _defaults2 = _interopRequireDefault(_defaults); var _propTypes = require('./prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _filterInputAttributes = require('./filter-input-attributes'); var _filterInputAttributes2 = _interopRequireDefault(_filterInputAttributes); var _input = require('./input'); var _input2 = _interopRequireDefault(_input); var _suggestList = require('./suggest-list'); var _suggestList2 = _interopRequireDefault(_suggestList); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* global window */ // Escapes special characters in user input for regex function escapeRegExp(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); } /** * Entry point for the Geosuggest component */ var Geosuggest = function (_React$Component) { _inherits(Geosuggest, _React$Component); /** * The constructor. Sets the initial state. * @param {Object} props The properties object. */ function Geosuggest(props) { _classCallCheck(this, Geosuggest); var _this = _possibleConstructorReturn(this, (Geosuggest.__proto__ || Object.getPrototypeOf(Geosuggest)).call(this, props)); _this.onInputChange = function (userInput) { _this.setState({ userInput: userInput }, _this.onAfterInputChange); }; _this.onAfterInputChange = function () { if (!_this.state.isSuggestsHidden) { _this.showSuggests(); } _this.props.onChange(_this.state.userInput); }; _this.onInputFocus = function () { _this.props.onFocus(); _this.showSuggests(); }; _this.onInputBlur = function () { if (!_this.state.ignoreBlur) { _this.hideSuggests(); } }; _this.onNext = function () { return _this.activateSuggest('next'); }; _this.onPrev = function () { return _this.activateSuggest('prev'); }; _this.onSelect = function () { return _this.selectSuggest(_this.state.activeSuggest); }; _this.onSuggestMouseDown = function () { return _this.setState({ ignoreBlur: true }); }; _this.onSuggestMouseOut = function () { return _this.setState({ ignoreBlur: false }); }; _this.onSuggestNoResults = function () { _this.props.onSuggestNoResults(_this.state.userInput); }; _this.hideSuggests = function () { _this.props.onBlur(_this.state.userInput); var timer = setTimeout(function () { _this.setState({ isSuggestsHidden: true, activeSuggest: null }); }, 100); _this.setState({ timer: timer }); }; _this.selectSuggest = function (suggest) { if (!suggest) { suggest = { label: _this.state.userInput }; } _this.setState({ isSuggestsHidden: true, userInput: suggest.label }); if (suggest.location) { _this.setState({ ignoreBlur: false }); _this.props.onSuggestSelect(suggest); return; } _this.geocodeSuggest(suggest); }; _this.state = { isSuggestsHidden: true, isLoading: false, userInput: props.initialValue, activeSuggest: null, suggests: [], timer: null }; _this.onInputChange = _this.onInputChange.bind(_this); _this.onAfterInputChange = _this.onAfterInputChange.bind(_this); if (props.queryDelay) { _this.onAfterInputChange = (0, _lodash2.default)(_this.onAfterInputChange, props.queryDelay); } return _this; } /** * Change inputValue if prop changes * @param {Object} props The new props */ _createClass(Geosuggest, [{ key: 'componentWillReceiveProps', value: function componentWillReceiveProps(props) { if (this.props.initialValue !== props.initialValue) { this.setState({ userInput: props.initialValue }); } } /** * Called on the client side after component is mounted. * Google api sdk object will be obtained and cached as a instance property. * Necessary objects of google api will also be determined and saved. */ }, { key: 'componentWillMount', value: function componentWillMount() { if (typeof window === 'undefined') { return; } var googleMaps = this.props.googleMaps || window.google && // eslint-disable-line no-extra-parens window.google.maps || this.googleMaps; /* istanbul ignore next */ if (!googleMaps) { console.error( // eslint-disable-line no-console 'Google map api was not found in the page.'); return; } this.googleMaps = googleMaps; this.autocompleteService = new googleMaps.places.AutocompleteService(); this.geocoder = new googleMaps.Geocoder(); } /** * When the component will unmount */ }, { key: 'componentWillUnmount', value: function componentWillUnmount() { clearTimeout(this.state.timer); } /** * When the input changed * @param {String} userInput The input value of the user */ /** * On After the input got changed */ /** * When the input gets focused */ /** * When the input gets blurred */ }, { key: 'focus', /** * Focus the input */ value: function focus() { this.refs.input.focus(); } /** * Update the value of the user input * @param {String} userInput the new value of the user input */ }, { key: 'update', value: function update(userInput) { this.setState({ userInput: userInput }); this.props.onChange(userInput); } /* * Clear the input and close the suggestion pane */ }, { key: 'clear', value: function clear() { this.setState({ userInput: '' }, this.hideSuggests); } /** * Search for new suggests */ }, { key: 'searchSuggests', value: function searchSuggests() { var _this2 = this; if (!this.state.userInput) { this.updateSuggests(); return; } var options = { input: this.state.userInput }; ['location', 'radius', 'bounds', 'types'].forEach(function (option) { if (_this2.props[option]) { options[option] = _this2.props[option]; } }); if (this.props.country) { options.componentRestrictions = { country: this.props.country }; } this.setState({ isLoading: true }, function () { _this2.autocompleteService.getPlacePredictions(options, function (suggestsGoogle) { _this2.setState({ isLoading: false }); _this2.updateSuggests(suggestsGoogle || [], // can be null function () { if (_this2.props.autoActivateFirstSuggest && !_this2.state.activeSuggest) { _this2.activateSuggest('next'); } }); }); }); } /** * Update the suggests * @param {Array} suggestsGoogle The new google suggests * @param {Function} callback Called once the state has been updated */ }, { key: 'updateSuggests', value: function updateSuggests() { var _this3 = this; var suggestsGoogle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; var callback = arguments[1]; var suggests = [], regex = new RegExp(escapeRegExp(this.state.userInput), 'gim'), skipSuggest = this.props.skipSuggest, maxFixtures = 10, fixturesSearched = 0, activeSuggest = null; this.props.fixtures.forEach(function (suggest) { if (fixturesSearched >= maxFixtures) { return; } if (!skipSuggest(suggest) && suggest.label.match(regex)) { fixturesSearched++; suggest.placeId = suggest.label; suggest.isFixture = true; suggests.push(suggest); } }); suggestsGoogle.forEach(function (suggest) { if (!skipSuggest(suggest)) { suggests.push({ label: _this3.props.getSuggestLabel(suggest), placeId: suggest.place_id, isFixture: false }); } }); activeSuggest = this.updateActiveSuggest(suggests); this.setState({ suggests: suggests, activeSuggest: activeSuggest }, callback); } /** * Return the new activeSuggest object after suggests have been updated * @param {Array} suggests The new list of suggests * @return {Object} The new activeSuggest **/ }, { key: 'updateActiveSuggest', value: function updateActiveSuggest() { var suggests = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; var activeSuggest = this.state.activeSuggest; if (activeSuggest) { var newSuggest = suggests.find(function (listedSuggest) { return activeSuggest.placeId === listedSuggest.placeId && activeSuggest.isFixture === listedSuggest.isFixture; }); activeSuggest = newSuggest || null; } return activeSuggest; } /** * Show the suggestions */ }, { key: 'showSuggests', value: function showSuggests() { this.searchSuggests(); this.setState({ isSuggestsHidden: false }); } /** * Hide the suggestions */ }, { key: 'activateSuggest', /** * Activate a new suggest * @param {String} direction The direction in which to activate new suggest */ value: function activateSuggest(direction) { // eslint-disable-line complexity if (this.state.isSuggestsHidden) { this.showSuggests(); return; } var suggestsCount = this.state.suggests.length - 1, next = direction === 'next'; var newActiveSuggest = null, newIndex = 0, i = 0; for (i; i <= suggestsCount; i++) { if (this.state.suggests[i] === this.state.activeSuggest) { newIndex = next ? i + 1 : i - 1; } } if (!this.state.activeSuggest) { newIndex = next ? 0 : suggestsCount; } if (newIndex >= 0 && newIndex <= suggestsCount) { newActiveSuggest = this.state.suggests[newIndex]; } this.props.onActivateSuggest(newActiveSuggest); this.setState({ activeSuggest: newActiveSuggest }); } /** * When an item got selected * @param {GeosuggestItem} suggest The selected suggest item */ }, { key: 'geocodeSuggest', /** * Geocode a suggest * @param {Object} suggest The suggest */ value: function geocodeSuggest(suggest) { var _this4 = this; this.geocoder.geocode(suggest.placeId && !suggest.isFixture ? { placeId: suggest.placeId } : { address: suggest.label }, function (results, status) { if (status === _this4.googleMaps.GeocoderStatus.OK) { var gmaps = results[0], location = gmaps.geometry.location; suggest.gmaps = gmaps; suggest.location = { lat: location.lat(), lng: location.lng() }; } _this4.props.onSuggestSelect(suggest); }); } /** * Render the view * @return {Function} The React element to render */ }, { key: 'render', value: function render() { var attributes = (0, _filterInputAttributes2.default)(this.props), classes = (0, _classnames2.default)('geosuggest', this.props.className, { 'geosuggest--loading': this.state.isLoading }), shouldRenderLabel = this.props.label && attributes.id, input = _react2.default.createElement(_input2.default, _extends({ className: this.props.inputClassName, ref: 'input', value: this.state.userInput, label: this.props.label, error: this.props.error, ignoreEnter: !this.state.isSuggestsHidden, ignoreTab: this.props.ignoreTab, style: this.props.style.input, onChange: this.onInputChange, onFocus: this.onInputFocus, onBlur: this.onInputBlur, onKeyPress: this.props.onKeyPress, onNext: this.onNext, onPrev: this.onPrev, onSelect: this.onSelect, onEscape: this.hideSuggests }, attributes)), suggestionsList = _react2.default.createElement(_suggestList2.default, { isHidden: this.state.isSuggestsHidden, style: this.props.style.suggests, suggestItemStyle: this.props.style.suggestItem, suggests: this.state.suggests, activeSuggest: this.state.activeSuggest, onSuggestNoResults: this.onSuggestNoResults, onSuggestMouseDown: this.onSuggestMouseDown, onSuggestMouseOut: this.onSuggestMouseOut, onSuggestSelect: this.selectSuggest }); return _react2.default.createElement( 'div', { className: classes }, _react2.default.createElement( 'div', { className: 'geosuggest__input-wrapper' }, shouldRenderLabel && _react2.default.createElement( 'label', { className: 'geosuggest__label', htmlFor: attributes.id }, this.props.label ), input ), _react2.default.createElement( 'div', { className: 'geosuggest__suggests-wrapper' }, suggestionsList ) ); } }]); return Geosuggest; }(_react2.default.Component); /** * Types for the properties * @type {Object} */ Geosuggest.propTypes = _propTypes2.default; /** * Default values for the properties * @type {Object} */ Geosuggest.defaultProps = _defaults2.default; exports.default = Geosuggest;