UNPKG

ldx-widgets

Version:

widgets

282 lines (261 loc) 10.6 kB
(function() { var DOWN_ARROW, ENTER, InputTypeAhead, PAGE_DOWN, PAGE_UP, React, SearchInput, TextInput, UP_ARROW, div, ref, ref1, ul; React = require('react'); SearchInput = React.createFactory(require('./search_input')); TextInput = React.createFactory(require('./text_input')); ref = require('../constants/keyboard'), UP_ARROW = ref.UP_ARROW, DOWN_ARROW = ref.DOWN_ARROW, ENTER = ref.ENTER, PAGE_UP = ref.PAGE_UP, PAGE_DOWN = ref.PAGE_DOWN; ref1 = React.DOM, div = ref1.div, ul = ref1.ul; /* Input Type Ahead Props @results - REQUIRED - Array Array of items that are printed in the results list @value - OPTIONAL - String | Number Value of input field @searchInterval - OPTIONAL - Number Number in milliseconds before firing onSearch, after user finishes typing @onChange - REQUIRED - Function Function that is fired when the value of the input changes @onSearch - REQUIRED - Function Function that is fired when the timer triggers after the set searchInterval @className - OPTIONAL - String CSS class applied to form wrapper @placeholder - OPTIONAL - String | Number Default placeholder value of text input @resultConfig - REQUIRED - Object @component: React component that's rendered for each result item @height: Height for each result item - should be evenly divisible into maxContainerHeight @onResultSelect: Function that is fired when a list item is selected - should be used to alter state and pass new values to 'value' prop @maxContainerHeight - OPTIONAL - Number | String The maximum height of the typeahead results container. The resultConfig.height property should be evenly divisible into maxContainerHeight */ InputTypeAhead = React.createClass({ displayName: 'InputTypeAhead', propTypes: { resultConfig: React.PropTypes.shape({ component: React.PropTypes.func.isRequired, height: React.PropTypes.number.isRequired, onResultSelect: React.PropTypes.func.isRequired }), onSearch: React.PropTypes.func.isRequired, onChange: React.PropTypes.func.isRequired, className: React.PropTypes.string, results: React.PropTypes.array.isRequired, minLength: React.PropTypes.number, value: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]), maxContainerHeight: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]), placeholder: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]), searchInterval: React.PropTypes.number }, getInitialState: function() { return { loading: false, selectedResultIndex: 0, value: this.props.value || '', showResults: false }; }, componentWillReceiveProps: function(nextProps) { var selectedResultIndex, showResults; showResults = nextProps.results.length > 0 && nextProps.value.length > 0; selectedResultIndex = showResults && nextProps.value !== this.props.value ? 0 : this.state.selectedResultIndex; return this.setState({ showResults: showResults, selectedResultIndex: selectedResultIndex, value: nextProps.value }); }, getDefaultProps: function() { return { className: '', results: [], minLength: 1, searchInterval: 300, maxContainerHeight: 175 }; }, handleClick: function(e) { return e.stopPropagation(); }, componentWillMount: function() { document.addEventListener('keydown', this.handleKeyDown); window.addEventListener('hashchange', this.close); return document.addEventListener('click', this.close); }, componentWillUnmount: function() { document.removeEventListener('keydown', this.handleKeyDown); window.removeEventListener('hashchange', this.close); return document.removeEventListener('click', this.close); }, close: function(e) { if (this.isMounted()) { if (e != null) { e.stopPropagation(); } return this.setState({ showResults: false }, (function(_this) { return function() { var base; return typeof (base = _this.props).onClose === "function" ? base.onClose() : void 0; }; })(this)); } }, calculateMaxVisibleResults: function() { var containerHeight, maxContainerHeight, ref2, resultConfig, results, resultsHeight; ref2 = this.props, resultConfig = ref2.resultConfig, results = ref2.results, maxContainerHeight = ref2.maxContainerHeight; resultsHeight = resultConfig.height * results.length; containerHeight = resultsHeight > maxContainerHeight ? maxContainerHeight : resultsHeight; return containerHeight / resultConfig.height; }, onResultSelect: function(result) { this.props.resultConfig.onResultSelect(result); return this.close(); }, handleKeyDown: function(e) { var loading, numResults, ref2, result, results, selectedResultIndex, showResults; results = this.props.results; ref2 = this.state, selectedResultIndex = ref2.selectedResultIndex, showResults = ref2.showResults, loading = ref2.loading; numResults = this.calculateMaxVisibleResults(); loading = false; if (loading || !showResults) { return; } switch (e.keyCode) { case DOWN_ARROW: e.preventDefault(); e.stopPropagation(); return this.traverseResults(1); case UP_ARROW: e.preventDefault(); e.stopPropagation(); return this.traverseResults(-1); case PAGE_DOWN: e.preventDefault(); e.stopPropagation(); return this.traverseResults(numResults); case PAGE_UP: e.preventDefault(); e.stopPropagation(); return this.traverseResults(-numResults); case ENTER: e.preventDefault(); e.stopPropagation(); result = results[selectedResultIndex]; return this.onResultSelect(result); } }, handleChange: function() { this.props.onChange(); return this.executeSearch(this.refs.input.getValue()); }, executeSearch: function(simpleTerm) { var length, minLength, onSearch, ref2, searchInterval; ref2 = this.props, onSearch = ref2.onSearch, minLength = ref2.minLength, searchInterval = ref2.searchInterval; length = simpleTerm.length; if (this.searchTimer != null) { clearInterval(this.searchTimer); } if (length === 0) { return this.setState({ value: '' }); } else if (length < minLength) { } else { if (!this.state.loading) { this.setState({ loading: true }); } return this.searchTimer = setTimeout((function(_this) { return function() { return typeof onSearch === "function" ? onSearch(simpleTerm) : void 0; }; })(this), searchInterval); } }, traverseResults: function(change) { var itemTop, newResult, ref2, resultConfig, results, resultsTop, scrollTop, selectedResultIndex; if (this.refs.results == null) { return; } ref2 = this.props, results = ref2.results, resultConfig = ref2.resultConfig; selectedResultIndex = this.state.selectedResultIndex; newResult = selectedResultIndex + change; resultsTop = this.refs.results.getBoundingClientRect().top; scrollTop = this.refs.results.scrollTop; if (results.length && newResult >= results.length) { newResult = results.length - 1; } if (newResult <= 0) { newResult = 0; } itemTop = this.refs.resultsList.children[newResult].getBoundingClientRect().top; if (itemTop + resultConfig.height > resultsTop + resultConfig.height || itemTop < resultsTop) { this.refs.results.scrollTop = newResult * resultConfig.height; } return this.setState({ selectedResultIndex: newResult }); }, getValue: function() { return this.refs.input.getValue(); }, render: function() { var className, headerComponent, i, index, len, loading, noResults, placeholder, ref2, ref3, result, resultConfig, resultItems, results, selectedResultIndex, showNoResults, showResults, value; ref2 = this.props, results = ref2.results, className = ref2.className, headerComponent = ref2.headerComponent, placeholder = ref2.placeholder, showNoResults = ref2.showNoResults, resultConfig = ref2.resultConfig; ref3 = this.state, selectedResultIndex = ref3.selectedResultIndex, loading = ref3.loading, value = ref3.value, showResults = ref3.showResults; resultItems = []; noResults = !results.length && showNoResults; loading = false; for (index = i = 0, len = results.length; i < len; index = ++i) { result = results[index]; resultItems.push(resultConfig.component({ key: index, selectedResultIndex: selectedResultIndex, index: index, height: resultConfig.height, maxVisibleRows: this.calculateMaxVisibleResults(), result: result, onResultSelect: resultConfig.onResultSelect })); } return div({ className: "input-type-ahead " + className, onClick: this.handleClick }, [ TextInput({ key: 'input', ref: 'input', autoComplete: false, value: value, placeholder: placeholder, loading: loading, onChange: this.handleChange, className: resultItems.length && !loading ? 'no-radius' : void 0 }), resultItems.length && !loading && (headerComponent != null) ? headerComponent({ key: 'header' }) : void 0, div({ key: 'blah', className: 'blah' }), !loading && showResults ? div({ key: 'results', ref: 'results', className: 'type-ahead-results', style: { height: this.calculateMaxVisibleResults() * resultConfig.height + 2 } }, [ resultItems.length ? ul({ key: 'results-list', ref: 'resultsList' }, resultItems) : noResults ? div({ className: 'no-results', key: 'no-results' }, 'No Results') : void 0 ]) : void 0 ]); } }); module.exports = InputTypeAhead; }).call(this);