Skip to content

Instantly share code, notes, and snippets.

@bastianwegge
Created November 9, 2018 08:57
Show Gist options
  • Save bastianwegge/3b48b7d3da5181d4053637d0ede2dd1d to your computer and use it in GitHub Desktop.
Save bastianwegge/3b48b7d3da5181d4053637d0ede2dd1d to your computer and use it in GitHub Desktop.
Rails React Autocomplete Search with detailed results
// *************
// This is the frontend search part
// *************
// THE STYLES THAT ARE USED BY THE COMPONENT
// STILL HERE BECAUSE WE POTENTIALLY EXTRACT THEM
let styles = {
item: {
padding: '2px 6px',
cursor: 'default'
},
highlightedItem: {
color: 'black',
background: '#dceffa',
cursor: 'pointer'
},
menu: {
border: 'solid 1px #ccc',
position: 'fixed',
background: 'white',
color: 'black'
}
}
// THE CLASS DEFINITION OF THE SEARCHFORM COMPONENT
// WE USE THE BASIC BOOTSTRAP STYLING FOR WIDTH & HEIGHT
class SearchForm extends React.Component {
constructor(props) {
super(props)
let initialValue = ''
if(props.options.value) {
initialValue = props.options.value
}
this.state = {
searchResults: [],
loading: false,
value: initialValue
}
// DEBOUNCING USER INPUT FOR 1 SECOND (1000ms)
this.searchFor = _.debounce(this.searchFor, 1000);
}
// ASYNCHRONOUS REQUEST (jQuery)
searchFor(value, callback) {
let search = value;
return $.ajax({
method: 'GET',
url: "/search.json",
dataType: 'JSON',
data: {
search,
locale // this is a global variable
},
success: callback
});
}
// HANDLE USER INPUT ON SEARCH FIELD
onInputChange(event, value) {
this.setState({ value });
// SKIP SEARCHES UNDER 3 CHARACTERS
if(!value || value.length < 3) {
return;
}
this.setState({ loading: true });
this.searchFor(value, (items) => {
this.setState({ searchResults: items, loading: false });
});
}
onSelectResult(value, item) {
this.setState({ value });
window.location.href = `/recipes/${item.id}`;
}
renderMenu(items, value, ownStyle) {
const {empty, loading, no_match} = this.props.options.search
return (
<div style={{...styles.menu, ...ownStyle}}>
{value === '' ? (
<div style={{padding: 6}}>{empty}</div>
) : this.state.loading ? (
<div style={{padding: 6}}>{loading}</div>
) : items.length === 0 ? (
<div style={{padding: 6}}>{no_match} {value}</div>
) : this.renderItems(items)}
</div>
)
}
renderItems (items) {
return items.map((item, index) => {
var text = item.props.children
return item
})
}
renderItem(item, highlighted) {
return (
<a className="recipe-result"
style={highlighted ? styles.highlightedItem : null}
href={`/recipes/${item.id}`}
key={item.id}>
<div className="image">
<img src={item.imageUrl} alt="Recipe image"/>
</div>
<div className="title">{item.title}</div>
</a>
)
}
render () {
const { placeholder } = this.props.options.search
// SETTINGS FOR ReactAutocomplete
const inputProps = {
title: "Recipe-Title",
id: "recipe-autocomplete",
name: "search",
className: "form-control",
placeholder: placeholder
}
return (
<div>
<ReactAutocomplete
inputProps={inputProps}
ref="autocomplete"
value={this.state.value}
items={this.state.searchResults}
getItemValue={(item) => item.title}
onSelect={this.onSelectResult.bind(this)}
onChange={this.onInputChange.bind(this)}
renderMenu={this.renderMenu.bind(this)}
renderItem={this.renderItem.bind(this)}
wrapperStyle={{display: 'block'}}
/>
</div>
)
}
}
SearchForm.propTypes = {
label: React.PropTypes.string,
options: React.PropTypes.object
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment