From bbb2598300731b8fa7d2264d43c097ea61c35121 Mon Sep 17 00:00:00 2001 From: Francis Giraldeau Date: Fri, 16 Dec 2016 18:15:33 -0500 Subject: [PATCH] Add geocoding requests with mapbox Signed-off-by: Francis Giraldeau --- react-client/package.json | 5 +- react-client/src/App.js | 7 +- react-client/src/Geocoder.js | 154 ++++++++++++++++++++++++++++++--- react-client/src/index.js | 7 ++ react-client/webpack.config.js | 0 5 files changed, 159 insertions(+), 14 deletions(-) create mode 100644 react-client/webpack.config.js diff --git a/react-client/package.json b/react-client/package.json index 57284b7..9c15d40 100644 --- a/react-client/package.json +++ b/react-client/package.json @@ -7,13 +7,16 @@ }, "dependencies": { "immutable": "^3.8.1", + "promise-polyfill": "^6.0.2", "react": "^15.4.1", + "react-autobind": "^1.0.6", "react-dom": "^15.4.1", "react-redux": "^4.4.6", "react-simple-range": "^1.4.0", "redux": "^3.6.0", "semantic-ui-css": "^2.2.4", - "semantic-ui-react": "^0.61.4" + "semantic-ui-react": "^0.61.4", + "whatwg-fetch": "^2.0.1" }, "scripts": { "start": "react-scripts start", diff --git a/react-client/src/App.js b/react-client/src/App.js index 6737166..0705681 100644 --- a/react-client/src/App.js +++ b/react-client/src/App.js @@ -5,6 +5,8 @@ import ParametersPannel from './ParametersPannel.js'; import CarSelector from './CarSelector.js'; import Geocoder from './Geocoder.js' +const accessToken = ''; + const carParameters = [ { id: "battery", label: "Battery capacity", @@ -36,7 +38,10 @@ class App extends Component { render() { return (
- + { console.log(event); }} + /> diff --git a/react-client/src/Geocoder.js b/react-client/src/Geocoder.js index 049bcbb..b08d564 100644 --- a/react-client/src/Geocoder.js +++ b/react-client/src/Geocoder.js @@ -1,13 +1,36 @@ import React, { Component } from 'react' import ReactDOM from 'react-dom' -import { Input } from 'semantic-ui-react' +import { List, Input } from 'semantic-ui-react' +import autoBind from 'react-autobind'; class Geocoder extends Component { constructor() { super(); + autoBind(this); this.state = this.getDefaultState(); } - + + search(endpoint, source, accessToken, proximity, bbox, types, query, callback) { + var searchTime = new Date(); + var uri = endpoint + '/geocoding/v5/' + + source + '/' + encodeURIComponent(query) + '.json' + + '?access_token=' + accessToken + + (proximity ? '&proximity=' + proximity : '') + + (bbox ? '&bbox=' + bbox : '') + + (types ? '&types=' + encodeURIComponent(types) : ''); + fetch(uri, { + headers: { + Accept: 'application/json', + }, + }).then(function(response) { + return response.json(); + }).then(function(json) { + callback(json, searchTime); + }).catch(function(ex) { + console.log("mapbox geocoding search error", ex); + }); + } + getDefaultState() { return { results: [], @@ -22,20 +45,127 @@ class Geocoder extends Component { ReactDOM.findDOMNode(this.refs.input).focus(); } + onInput(event) { + this.setState({loading: true}); + var value = event.target.value; + if (value === '') { + this.setState(this.getDefaultState()); + } else { + this.search( + this.props.endpoint, + this.props.source, + this.props.accessToken, + this.props.proximity, + this.props.bbox, + this.props.types, + value, + this.onResult); + } + } + + moveFocus(dir) { + if (this.state.loading) + return; + + var focus = 0; + if (this.state.focus !== null) { + focus = Math.max(0, Math.min(this.state.results.length - 1, this.state.focus + dir)) + } + console.log("focus", focus); + this.setState({ + focus: focus, + }); + } + + acceptFocus() { + if (this.state.focus !== null) { + this.props.onSelect(this.state.results[this.state.focus]); + } + } + + onKeyDown(event) { + switch (event.which) { + // up + case 38: + event.preventDefault(); + this.moveFocus(-1); + break; + // down + case 40: + this.moveFocus(1); + break; + // accept + case 13: + if (this.state.results.length > 0 && this.state.focus == null) { + this.clickOption(this.state.results[0], 0); + } + this.acceptFocus(); + break; + default: + break; + } + } + + onResult(body, searchTime) { + // searchTime is compared with the last search to set the state + // to ensure that a slow xhr response does not scramble the + // sequence of autocomplete display. + if (body && body.features && this.state.searchTime <= searchTime) { + this.setState({ + searchTime: searchTime, + loading: false, + results: body.features, + focus: null + }); + this.props.onSuggest(this.state.results); + } + } + clickOption(place, listLocation) { + this.props.onSelect(place); + this.setState({focus: listLocation}); + // focus on the input after click to maintain key traversal + ReactDOM.findDOMNode(this.refs.input).focus(); + return false; + } render() { + var resultList = null; + if (this.state.results.length > 0) { + const items = this.state.results.map((result, i) => { + return ( + + + + {result.place_name} + + + + ); + }); + + resultList = ( + + {items} + + ); + } + return ( - { - console.log(event.target.value); - }} - /> +
+ + {resultList} +
); } } diff --git a/react-client/src/index.js b/react-client/src/index.js index 54c5ef1..cc19ae0 100644 --- a/react-client/src/index.js +++ b/react-client/src/index.js @@ -1,8 +1,15 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; +import 'whatwg-fetch' +import Promise from 'promise-polyfill'; import './index.css'; +// To add to window +if (!window.Promise) { + window.Promise = Promise; +} + ReactDOM.render( , document.getElementById('root') diff --git a/react-client/webpack.config.js b/react-client/webpack.config.js new file mode 100644 index 0000000..e69de29