UNPKG

appirio-tech-react-components-test10

Version:

Topcoder UI Kit library of components for our React apps.

260 lines (232 loc) 8.43 kB
require('./SearchBar.scss') import React, { Component } from 'react' import PropTypes from 'prop-types' import ReactDOM from 'react-dom' import SearchSuggestions from '../SearchSuggestions/SearchSuggestions' import Loader from '../Loader/Loader' import classNames from 'classnames' //states: empty, filled, focused class SearchBar extends Component { constructor(props) { super(props) const initialTerm = this.getQueryStringValue(props.searchTermKey) this.state = { searchState: initialTerm.length > 0 ? 'filled' : 'empty', suggestions: [], searchValue: initialTerm, finalTerm: initialTerm } this.onFocus = this.onFocus.bind(this) this.onChange = this.onChange.bind(this) this.onKeyUp = this.onKeyUp.bind(this) this.clearSearch = this.clearSearch.bind(this) this.handleClick = this.handleClick.bind(this) this.search = this.search.bind(this) this.handleSuggestionSelect = this.handleSuggestionSelect.bind(this) this.handleOutsideClick = this.handleOutsideClick.bind(this) this.handleSuggestionsUpdate = this.handleSuggestionsUpdate.bind(this) } getQueryStringValue (key) { return unescape(window.location.href.replace(new RegExp('^(?:.*[&\\?]' + escape(key).replace(/[\.\+\*]/g, '\\$&') + '(?:\\=([^&]*))?)?.*$', 'i'), '$1')) } componentDidMount() { window.addEventListener('click', this.handleOutsideClick) } componentWillUnmount() { window.removeEventListener('click', this.handleOutsideClick) } componentWillReceiveProps(nextProps) { const searchVal = this.getQueryStringValue(nextProps.searchTermKey) if (searchVal !== this.state.searchValue) { this.setState({ searchState: 'filled', searchValue: searchVal }) } } handleOutsideClick(evt) { let t = evt.target let i = 0 const searchBarNode = ReactDOM.findDOMNode(this) let clickedInside = false while(t !== null && i < 10) { i++ if (t === searchBarNode) { clickedInside = true break } t = t.parentNode } if (!clickedInside) { if(this.state.searchValue) { this.setState({ searchState: 'filled' }) } else { if (this.state.finalTerm && this.state.finalTerm.trim().length > 0) { this.setState({ searchState: 'filled', searchValue: this.state.finalTerm }) } else { this.setState({ searchState: 'empty' }) } } } } onFocus() { this.setState({ searchState: 'focused' }) } blur() { this.refs.searchValue.blur() } handleSuggestionsUpdate(requestNo, data) { if (requestNo === this.state.maxRequestNo) { console.log('SUGGESTIONS', data) this.setState({loading: false, suggestions: data, selectedSuggestionIdx: null}) } } onChange() { const oldTerm = this.state.searchValue const newTerm = this.refs.searchValue.value ? this.refs.searchValue.value.trim() : '' this.setState( function(prevState) { const rc = prevState.requestNo ? prevState.requestNo + 1 : 1 return { searchValue: this.refs.searchValue.value, requestNo: rc, maxRequestNo: rc, loading: newTerm.length > 0, searchState: 'focused' } }, function() { if (newTerm.length > 0) { this.props.onTermChange.apply(null, [ oldTerm, newTerm, this.state.requestNo, this.handleSuggestionsUpdate ]) } } ) } clearSearch() { this.refs.searchValue.value = '' this.setState({ searchValue: this.refs.searchValue.value, finalTerm: '' }) this.setState({ searchState: 'empty' }) this.setState({ suggestions: false }) this.props.onClearSearch() } onKeyUp(evt) { const eventKey = evt.keyCode evt.stopPropagation() evt.preventDefault() // if return is pressed if (eventKey === 13) { this.setState({ searchState: 'filled', finalTerm: this.state.searchValue }, function() { this.search() this.blur() }) } else if (eventKey === 39) { // right arrow key is pressed const suggestion = this.state.suggestions[0] if (suggestion) { this.refs.searchValue.value = suggestion // trigger the change event handler this.onChange() } } else if (eventKey === 38) { // up arrow key const currSelectedIdx = this.state.selectedSuggestionIdx if (currSelectedIdx) { // index is none of (undefined, null, 0) const suggestionIdx = currSelectedIdx - 1 const suggestion = this.state.suggestions[suggestionIdx] this.refs.searchValue.value = suggestion this.setState({ selectedSuggestionIdx : suggestionIdx, searchValue: suggestion }) } } else if (eventKey === 40) { // down arrow key const currSelectedIdx = this.state.selectedSuggestionIdx // index is none of (undefined, null, 0) if (typeof currSelectedIdx === 'undefined' || currSelectedIdx === null || this.state.suggestions.length > currSelectedIdx + 1) { const suggestionIdx = typeof currSelectedIdx === 'number' ? currSelectedIdx + 1 : 0 const suggestion = this.state.suggestions[suggestionIdx] this.refs.searchValue.value = suggestion this.setState({ selectedSuggestionIdx: suggestionIdx, searchValue: suggestion }) } } } handleSuggestionSelect(selectedTerm) { this.setState({ searchValue: selectedTerm, searchState: 'filled' }, function() { this.search() }) } search() { const searchTerm = this.state.finalTerm ? this.state.finalTerm.trim() : '' if(searchTerm.length > 0) { this.props.onSearch.apply(this, [searchTerm]) } } handleClick() { if(this.state.searchValue.length > 0) { this.setState({ searchState: 'filled', finalTerm: this.state.searchValue }, () => { this.search() }) } } render() { const recentList = this.props.recentTerms const popularList = this.state.suggestions const searchState = this.state.searchState const searchValue = this.state.searchValue let typeaheadText = '' if(searchValue) { for(let i = 0; i < popularList.length; i++) { const idx = popularList[i].toLowerCase().indexOf(searchValue.toLowerCase()) // show typeahead hint only if the search term matched at 0 index if(!typeaheadText && idx === 0) { typeaheadText = searchValue + popularList[i].substring(searchValue.length) } } } else { typeaheadText = '' } const sbClasses = classNames('SearchBar', { 'state-empty' : searchState === 'empty', 'state-focused': searchState === 'focused', 'state-filled' : searchState === 'filled' }) const results = this.state.loading === true ? <div className="loading-suggestions"><Loader /></div> : <SearchSuggestions hideSuggestionsWhenEmpty={ this.props.hideSuggestionsWhenEmpty } recentSearch={ recentList } searchTerm={ this.state.searchValue } popularSearch={ popularList } showPopularSearchHeader={ this.props.showPopularSearchHeader } onSuggestionSelect={ this.handleSuggestionSelect } /> return ( <div className={ sbClasses }> <span className="search-typeahead-text">{ typeaheadText }</span> <input className="search-bar__text" onFocus={ this.onFocus } onChange={ this.onChange } onKeyUp={ this.onKeyUp } ref="searchValue" value={this.state.searchValue} /> <img className="search-bar__clear" src={ require('./x-mark.svg') } onClick={ this.clearSearch }/> <div className="search-icon-wrap" onClick={ this.handleClick }> <span className="search-txt">Search</span> </div> <div className="suggestions-panel"> {results} </div> </div> ) } } SearchBar.propTypes = { hideSuggestionsWhenEmpty: PropTypes.bool, onSearch : PropTypes.func.isRequired, onClearSearch : PropTypes.func, onTermChange : PropTypes.func.isRequired, recentTerms : PropTypes.array, searchTermKey: PropTypes.string, showPopularSearchHeader: PropTypes.bool } SearchBar.defaultProps = { hideSuggestionsWhenEmpty: false, recentTerms : [], searchTermKey : 'q', onClearSearch : () => {}, showPopularSearchHeader: true } export default SearchBar