Skip to content

Commit a8fd10c

Browse files
author
David Emory
committed
feat(form): Initial work on new location field, including geocoding
1 parent 6841ce2 commit a8fd10c

File tree

2 files changed

+150
-18
lines changed

2 files changed

+150
-18
lines changed

lib/components/form/location-field.js

Lines changed: 149 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,41 @@
11
import lonlat from '@conveyal/lonlat'
22
import React, { Component, PropTypes } from 'react'
3-
import { FormGroup } from 'react-bootstrap'
3+
import { FormGroup, FormControl, InputGroup, DropdownButton, MenuItem } from 'react-bootstrap'
44
import { connect } from 'react-redux'
5-
import Geocoder from 'react-select-geocoder'
5+
import { autocomplete } from 'isomorphic-mapzen-search'
66

77
import { setLocation, clearLocation } from '../../actions/map'
88

99
class LocationField extends Component {
1010
static propTypes = {
11+
config: PropTypes.object,
1112
location: PropTypes.object,
1213
label: PropTypes.string,
13-
setLocation: PropTypes.func,
14-
type: PropTypes.string // replace with locationType?
14+
type: PropTypes.string, // replace with locationType?
15+
16+
// dispatch
17+
clearLocation: PropTypes.func,
18+
setLocation: PropTypes.func
1519
}
20+
1621
constructor (props) {
1722
super(props)
18-
this.state = {}
23+
this.state = {
24+
dropdownOpen: false,
25+
geocodedFeatures: []
26+
}
27+
}
28+
29+
componentWillReceiveProps (nextProps) {
30+
if (this.props.location !== nextProps.location) {
31+
this.setState({
32+
value: nextProps.location !== null ? nextProps.location.name : '',
33+
geocodedFeatures: []
34+
})
35+
}
1936
}
20-
_onChange = (value, option) => {
37+
38+
/* _onChange = (value, option) => {
2139
console.log(value, option)
2240
if (value && value.geometry) {
2341
const location = lonlat.fromCoordinates(value.geometry.coordinates)
@@ -32,34 +50,147 @@ class LocationField extends Component {
3250
this.props.clearLocation(this.props.type)
3351
}
3452
}
53+
3554
_toValue = (location) => {
3655
return location && {
3756
value: `${location.lon},${location.lat}`,
3857
label: location.name
3958
}
59+
} */
60+
61+
_geocode (text) {
62+
const { config } = this.props
63+
autocomplete({
64+
apiKey: config.geocoder.MAPZEN_KEY,
65+
boundary: config.geocoder.boundary,
66+
// TODO: use current location as focus point
67+
text
68+
}).then((result) => {
69+
console.log(result)
70+
this.setState({ geocodedFeatures: result.features })
71+
}).catch((err) => {
72+
console.error(err)
73+
})
4074
}
75+
4176
render () {
42-
const { config, location } = this.props
43-
// TODO: add geolocation (in react-select-geocoder?)
77+
const currentLocationOption = createOption('location-arrow', 'Use Current Location', 'test1')
78+
79+
let geocodedFeatures = this.state.geocodedFeatures
80+
if (geocodedFeatures.length > 5) geocodedFeatures = geocodedFeatures.splice(0, 5)
81+
82+
let nearbyStops = [] /*{
83+
name: 'Stop X',
84+
routes: ['10','11']
85+
}]*/
86+
87+
const formControlClassname = this.props.type + '-form-control'
4488
return (
4589
<form>
46-
<FormGroup>
47-
<Geocoder
48-
apiKey={config.geocoder.MAPZEN_KEY}
49-
boundary={config.geocoder.boundary}
50-
placeholder={this.props.label || this.props.type}
51-
focusPoint={[config.map.initLon, config.map.initLat]}
52-
value={this._toValue(location)}
53-
onChange={this._onChange}
54-
geolocate
55-
// optionRenderer={location === null ? (option) => <span><Icon type={option.icon} /> {option.label}</span> : undefined}
90+
<FormGroup className='location-field'>
91+
92+
<InputGroup>
93+
{/* location field icon -- also serves as dropdown anchor */}
94+
<DropdownButton
95+
componentClass={InputGroup.Button}
96+
open={this.state.dropdownOpen}
97+
onToggle={(v, e) => {
98+
// if clicked on input form control, keep dropdown open; otherwise, toggle
99+
const targetIsInput = e.target.className.indexOf(formControlClassname) !== -1
100+
this.setState({ dropdownOpen: targetIsInput ? true : !this.state.dropdownOpen })
101+
}}
102+
id='location-dropdown'
103+
title={<i className='fa fa-star' />}
104+
noCaret
105+
>
106+
{/* current location option */}
107+
{createOption('location-arrow', 'Use Current Location')}
108+
109+
{/* geocode search result option(s) */}
110+
{geocodedFeatures.length > 0 && <MenuItem header>Search Results</MenuItem>}
111+
{geocodedFeatures.length > 0 &&
112+
geocodedFeatures.map(feature => {
113+
return createOption('map-pin', feature.properties.label, () => {
114+
const location = lonlat.fromCoordinates(feature.geometry.coordinates)
115+
location.name = feature.properties.label
116+
this.props.setLocation(this.props.type, location)
117+
})
118+
})
119+
}
120+
121+
{/* nearby transit stop options */}
122+
{nearbyStops.length > 0 && <MenuItem header>Nearby Stops</MenuItem>}
123+
{nearbyStops.length > 0 &&
124+
nearbyStops.map(stop => {
125+
return createTransitStopOption(stop.name, stop.routes, () => {
126+
// set location from stop
127+
})
128+
})
129+
}
130+
{/* recent search history options */}
131+
132+
</DropdownButton>
133+
<FormControl ref='formControl'
134+
className={formControlClassname}
135+
type='text'
136+
value={this.state.value}
137+
placeholder={this.props.label || this.props.type}
138+
onChange={(e) => {
139+
this.setState({ value: e.target.value })
140+
this._geocode(e.target.value)
141+
}}
142+
onClick={() => {
143+
this.setState({ dropdownOpen: true })
144+
}}
56145
/>
146+
<InputGroup.Addon onClick={() => {
147+
this.props.clearLocation(this.props.type)
148+
this.setState({
149+
value: '',
150+
geocodedFeatures: []
151+
})
152+
}}>
153+
<i className='fa fa-times' />
154+
</InputGroup.Addon>
155+
</InputGroup>
57156
</FormGroup>
58157
</form>
59158
)
60159
}
61160
}
62161

162+
// helper functions for dropdown options
163+
164+
function createOption (icon, title, onSelect) {
165+
return <MenuItem onSelect={onSelect}>
166+
<div>
167+
<div style={{ float: 'left' }}><i className={`fa fa-${icon}`} /></div>
168+
<div style={{ marginLeft: '30px' }}>{title}</div>
169+
</div>
170+
</MenuItem>
171+
}
172+
173+
function createTransitStopOption (name, routes, onSelect) {
174+
return <MenuItem onSelect={onSelect}>
175+
<div>
176+
<div style={{ float: 'left', paddingTop: '3px' }}>
177+
<i className='fa fa-bus' style={{ fontSize: '20px' }} />
178+
<div style={{ fontSize: '8px' }}>0.2 mi</div>
179+
</div>
180+
<div style={{ marginLeft: '30px' }}>
181+
<div>{name}</div>
182+
<div style={{ fontSize: '9px' }}>
183+
{routes.map(route => {
184+
return <span style={{ backgroundColor: 'gray', color: 'white', lineHeight: '9px', padding: '0px 3px', marginRight: '5px' }}>{route}</span>
185+
})}
186+
</div>
187+
</div>
188+
</div>
189+
</MenuItem>
190+
}
191+
192+
// connect to redux store
193+
63194
const mapStateToProps = (state, ownProps) => {
64195
return {
65196
config: state.otp.config,

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"font-awesome": "^4.7.0",
3636
"immutability-helper": "^2.1.1",
3737
"isomorphic-fetch": "^2.2.1",
38+
"isomorphic-mapzen-search": "^1.2.0",
3839
"leaflet": "^1.0.3",
3940
"lodash.debounce": "^4.0.8",
4041
"moment": "^2.17.1",

0 commit comments

Comments
 (0)