colouring-montreal/app/src/frontend/map/search-box.tsx

185 lines
5.8 KiB
TypeScript
Raw Normal View History

2019-02-11 04:04:19 -05:00
import React, { Component } from 'react';
2019-05-27 13:26:29 -04:00
import PropTypes from 'prop-types';
2019-02-11 04:04:19 -05:00
import './search-box.css';
import { SearchIcon } from '../components/icons';
2019-02-11 04:04:19 -05:00
/**
* Search for location
*/
class SearchBox extends Component<any, any> { // TODO: add proper types
static propTypes = { // TODO: generate propTypes from TS
2019-09-08 19:44:26 -04:00
onLocate: PropTypes.func
};
2019-02-11 04:04:19 -05:00
constructor(props) {
super(props);
this.state = {
2019-05-27 11:31:48 -04:00
q: '',
2019-02-11 04:04:19 -05:00
results: [],
fetching: false,
//track the state of the search box i.e. collapsed or expanded. Default to true
collapsedSearch: true,
//is this a small screen device? if not we will disable collapse option
smallScreen: false
2019-02-11 04:04:19 -05:00
}
this.handleChange = this.handleChange.bind(this);
this.search = this.search.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
this.clearResults = this.clearResults.bind(this);
this.clearQuery = this.clearQuery.bind(this);
this.expandSearch = this.expandSearch.bind(this);
this.onResize= this.onResize.bind(this);
2019-02-11 04:04:19 -05:00
}
// Update search term
handleChange(e) {
this.setState({
q: e.target.value
});
2019-05-27 13:26:29 -04:00
// If the clear icon has been clicked, clear results list as well
2019-05-27 11:31:48 -04:00
if(e.target.value === '') {
2019-05-27 11:39:16 -04:00
this.clearResults();
}
}
// Clear search results on ESC
handleKeyPress(e){
if(e.keyCode === 27) {
//ESC is pressed
this.clearQuery();
this.clearResults();
}
}
clearResults(){
this.setState({
results: []
});
}
clearQuery(){
this.setState({
2019-05-27 11:31:48 -04:00
q: ''
});
2019-02-11 04:04:19 -05:00
}
expandSearch(e){
this.setState(state => ({
collapsedSearch: !state.collapsedSearch
}));
}
2019-02-11 04:04:19 -05:00
// Query search endpoint
search(e) {
e.preventDefault();
this.setState({
fetching: true
})
fetch(
2019-08-14 09:05:49 -04:00
'/api/search?q='+this.state.q
2019-02-11 04:04:19 -05:00
).then(
(res) => res.json()
).then((data) => {
if (data && data.results){
this.setState({
results: data.results,
fetching: false
})
} else {
console.error(data);
this.setState({
results: [],
fetching: false
})
}
}).catch((err) => {
2019-05-27 11:39:16 -04:00
console.error(err)
2019-02-11 04:04:19 -05:00
2019-05-27 11:39:16 -04:00
this.setState({
results: [],
fetching: false
})
2019-02-11 04:04:19 -05:00
})
}
componentDidMount() {
window.addEventListener('resize', this.onResize);
if (window && window.innerHeight) {
// if we're in the browser, pass in as though from event to initialise
this.onResize({target: window});
}
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResize);
}
// On a real mobile device onResize() gets called when the virtual keyboard pops up (e.g. when entering search text)
// so be careful what states are changed in this method (i.e. don't collapse the search box here)
onResize(e) {
this.setState({smallScreen: (e.target.innerWidth < 768)});
}
2019-02-11 04:04:19 -05:00
render() {
// if the current state is collapsed (and a mobile device) just render the icon
if(this.state.collapsedSearch && this.state.smallScreen){
return(
<div className="collapse-btn" onClick={this.expandSearch}>
<SearchIcon />
</div>
)
}
2019-02-11 04:04:19 -05:00
const resultsList = this.state.results.length?
<ul className="search-box-results">
2019-05-27 11:39:16 -04:00
{
this.state.results.map((result) => {
const label = result.attributes.label;
const lng = result.geometry.coordinates[0];
const lat = result.geometry.coordinates[1];
const zoom = result.attributes.zoom;
const href = `?lng=${lng}&lat=${lat}&zoom=${zoom}`
return (
<li key={result.attributes.label}>
<a
className="search-box-result"
onClick={(e) => {
e.preventDefault();
this.props.onLocate(lat, lng, zoom);
}}
href={href}
2019-05-27 13:26:29 -04:00
>{`${label.substring(0, 4)} ${label.substring(4, 7)}`}</a>
2019-05-27 11:39:16 -04:00
</li>
)
})
}
2019-02-11 04:04:19 -05:00
</ul>
2019-05-27 11:39:16 -04:00
: null;
2019-02-11 04:04:19 -05:00
return (
2019-09-08 19:44:26 -04:00
<div className="search-box" onKeyDown={this.handleKeyPress}>
<form onSubmit={this.search} className="form-inline">
<div onClick={this.state.smallScreen ? this.expandSearch : null}>
<SearchIcon/>
</div>
2019-02-11 04:04:19 -05:00
<input
className="form-control"
type="search"
id="search-box-q"
name="q"
value={this.state.q}
placeholder="Search for a postcode"
aria-label="Search for a postcode"
onChange={this.handleChange}
2019-05-27 11:39:16 -04:00
/>
<button className="btn btn-outline-dark" type="submit">Search</button>
2019-02-11 04:04:19 -05:00
</form>
{ resultsList }
</div>
)
}
}
export default SearchBox;