colouring-montreal/app/src/frontend/search-box.tsx
Maciej Ziarkowski 8b78a74874 Remove unnecessary form action/method
The forms calling the API had an unnecessary form action and method.
This is because the forms have an onSubmit hook calling preventDefault.
The attributes were removed to avoid confusion.
2019-08-15 12:19:43 +01:00

186 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './search-box.css';
import { SearchIcon } from './icons';
/**
* Search for location
*/
class SearchBox extends Component<any, any> { // TODO: add proper types
static propTypes = { // TODO: generate propTypes from TS
onLocate: PropTypes.func,
isBuilding: PropTypes.bool
};
constructor(props) {
super(props);
this.state = {
q: '',
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
}
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);
}
// Update search term
handleChange(e) {
this.setState({
q: e.target.value
});
// If the clear icon has been clicked, clear results list as well
if(e.target.value === '') {
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({
q: ''
});
}
expandSearch(e){
this.setState(state => ({
collapsedSearch: !state.collapsedSearch
}));
}
// Query search endpoint
search(e) {
e.preventDefault();
this.setState({
fetching: true
})
fetch(
'/api/search?q='+this.state.q
).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) => {
console.error(err)
this.setState({
results: [],
fetching: false
})
})
}
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)});
}
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>
)
}
const resultsList = this.state.results.length?
<ul className="search-box-results">
{
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}
>{`${label.substring(0, 4)} ${label.substring(4, 7)}`}</a>
</li>
)
})
}
</ul>
: null;
return (
<div className={`search-box ${this.props.isBuilding? 'building' : ''}`} onKeyDown={this.handleKeyPress}>
<form onSubmit={this.search} className="form-inline">
<div onClick={this.state.smallScreen ? this.expandSearch : null}>
<SearchIcon/>
</div>
<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}
/>
<button className="btn btn-outline-dark" type="submit">Search</button>
</form>
{ resultsList }
</div>
)
}
}
export default SearchBox;