UNPKG

react-geosuggest-sw

Version:

A React autosuggest for the Google Maps Places API.

336 lines (294 loc) 8.78 kB
/* global google */ 'use strict'; var React = require('react'), GeosuggestItem = require('./GeosuggestItem'); var Geosuggest = React.createClass({ displayName: 'Geosuggest', /** * Get the default props * @return {Object} The state */ getDefaultProps: function getDefaultProps() { return { fixtures: [], initialValue: '', placeholder: 'Search places', className: '', onSuggestSelect: function onSuggestSelect() {}, location: null, radius: 0, defaultIconClass: '', googleMaps: google && google.maps }; }, propTypes: { initialLocation: React.PropTypes.object }, /** * Get the initial state * @return {Object} The state */ getInitialState: function getInitialState() { return { isSuggestsHidden: true, userInput: this.props.initialValue, lastSuggest: { label: this.props.initialValue, location: this.props.initialLocation }, mouseHoveredSuggest: null, activeSuggest: null, suggests: [], geocoder: new this.props.googleMaps.Geocoder(), autocompleteService: new this.props.googleMaps.places.AutocompleteService() }; }, /** * When the input got changed */ onInputChange: function onInputChange() { var userInput = this.refs.geosuggestInput.getDOMNode().value; this.setState({ userInput: userInput }, (function () { this.showSuggests(); }).bind(this)); }, /** * Search for new suggests */ searchSuggests: function searchSuggests() { if (!this.state.userInput) { this.updateSuggests(); return; } this.state.autocompleteService.getPlacePredictions({ input: this.state.userInput, location: this.props.location || new this.props.googleMaps.LatLng(0, 0), radius: this.props.radius, componentRestrictions: { country: 'usa' } }, (function (suggestsGoogle) { this.updateSuggests(suggestsGoogle); }).bind(this)); }, /** * Update the suggests * @param {Object} suggestsGoogle The new google suggests */ updateSuggests: function updateSuggests(suggestsGoogle) { if (!suggestsGoogle) { suggestsGoogle = []; } var suggests = [], regex = new RegExp(this.state.userInput, 'gim'); // always display fixtures this.props.fixtures.forEach(function (suggest) { suggest.placeId = suggest.label; suggests.push(suggest); }); suggestsGoogle.forEach(function (suggest) { suggests.push({ label: suggest.description.replace(', United States', ''), placeId: suggest.place_id }); }); this.setState({ suggests: suggests }); }, /** * When the input gets focused */ showSuggests: function showSuggests() { this.searchSuggests(); this.setState({ isSuggestsHidden: false }); }, /** * When the input loses focused */ hideSuggests: function hideSuggests() { setTimeout((function () { this.setState({ isSuggestsHidden: true }); }).bind(this), 100); }, /** * When a key gets pressed in the input * @param {Event} event The keypress event */ onInputKeyDown: function onInputKeyDown(event) { switch (event.which) { case 40: // DOWN event.preventDefault(); this.activateSuggest('next'); break; case 38: // UP event.preventDefault(); this.activateSuggest('prev'); break; case 13: // ENTER this.selectSuggest(this.state.activeSuggest); break; case 9: // TAB this.selectSuggest(this.state.activeSuggest); break; case 27: // ESC this.hideSuggests(); break; default: break; } }, /** * Activate a new suggest * @param {String} direction The direction in which to activate new suggest */ activateSuggest: function activateSuggest(direction) { if (this.state.isSuggestsHidden) { this.showSuggests(); return; } var suggestsCount = this.state.suggests.length - 1, next = direction === 'next', 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.setState({ activeSuggest: newActiveSuggest }); }, /** * When an item got selected * @param {GeosuggestItem} suggest The selected suggest item */ selectSuggest: function selectSuggest(suggest) { if (!suggest) { // make the suggest the first non-fixture, if exists if (this.state.userInput && this.state.suggests.length - this.props.fixtures.length > 0) { suggest = this.state.suggests[this.props.fixtures.length]; } else { suggest = this.state.lastSuggest || {}; } } this.setState({ isSuggestsHidden: true, userInput: suggest.label, lastSuggest: suggest }); if (suggest.location) { this.props.onSuggestSelect(suggest); return; } this.geocodeSuggest(suggest); }, setMouseHoveredSuggest: function setMouseHoveredSuggest(suggest) { this.setState({ mouseHoveredSuggest: suggest }); }, /** * Geocode a suggest * @param {Object} suggest The suggest */ geocodeSuggest: function geocodeSuggest(suggest) { this.state.geocoder.geocode({ address: suggest.label }, (function (results, status) { if (status !== this.props.googleMaps.GeocoderStatus.OK) { return; } var gmaps = results[0], location = gmaps.geometry.location; suggest.gmaps = gmaps; suggest.location = { lat: location.lat(), lng: location.lng() }; this.props.onSuggestSelect(suggest); }).bind(this)); }, onBlur: function onBlur(e) { e.stopPropagation(); e.preventDefault(); this.selectSuggest(this.state.mouseHoveredSuggest); this.hideSuggests(); }, onFocus: function onFocus(e) { e.stopPropagation(); e.preventDefault(); this.setState({ userInput: '' }); // reset user input to empty when clicking into box this.showSuggests(); }, /** * Render the view * @return {Function} The React element to render */ render: function render() { return (// eslint-disable-line no-extra-parens React.createElement( 'div', { className: 'geosuggest ' + this.props.className, onClick: this.onClick }, React.createElement('input', { className: 'geosuggest__input', ref: 'geosuggestInput', type: 'text', value: this.state.userInput, placeholder: this.props.placeholder, onKeyDown: this.onInputKeyDown, onChange: this.onInputChange, onFocus: this.onFocus, onBlur: this.onBlur }), React.createElement( 'ul', { className: this.getSuggestsClasses() }, this.getSuggestItems() ) ) ); }, /** * Get the suggest items for the list * @return {Array} The suggestions */ getSuggestItems: function getSuggestItems() { var that = this; return this.state.suggests.map((function (suggest) { var isActive = this.state.activeSuggest && suggest.placeId === this.state.activeSuggest.placeId; return (// eslint-disable-line no-extra-parens React.createElement(GeosuggestItem, { key: suggest.placeId, suggest: suggest, isActive: isActive, onSuggestSelect: this.selectSuggest, setMouseHoveredSuggest: that.setMouseHoveredSuggest, classDecorations: this.itemClassDecorations(suggest) }) ); }).bind(this)); }, itemClassDecorations: function itemClassDecorations(suggest) { var fixtures = this.props.fixtures; var decorations = ''; // allow styling of last fixture if exists if (fixtures.length > 0) { decorations += fixtures.indexOf(suggest) + 1 === fixtures.length ? ' geosuggest-item-last-fixture' : ''; } decorations += suggest.iconClass ? ' ' + suggest.iconClass : ' ' + this.props.defaultIconClass; return decorations; }, /** * The classes for the suggests list * @return {String} The classes */ getSuggestsClasses: function getSuggestsClasses() { var classes = 'geosuggest__suggests'; classes += this.state.isSuggestsHidden ? ' geosuggest__suggests--hidden' : ''; return classes; } }); module.exports = Geosuggest;