UNPKG

ldx-widgets

Version:

widgets

324 lines (296 loc) 12.5 kB
(function() { var DOWN_ARROW, ENTER, InputTypeAhead, PAGE_DOWN, PAGE_UP, PropTypes, React, TextInput, UP_ARROW, button, createClass, div, ref, ref1, ul; React = require('react'); PropTypes = require('prop-types'); createClass = require('create-react-class'); TextInput = React.createFactory(require('./text_input_2')); 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 = require('react-dom-factories'), div = ref1.div, ul = ref1.ul, button = ref1.button; /*& @props.results - REQUIRED - [Array] Array of items that are printed in the results list @props.value - REQUIRED - [String | Number] Value of input field @props.searchInterval - OPTIONAL - [Number] Number in milliseconds before firing onSearch, after user finishes typing @props.onChange - REQUIRED - [Function] Function that is fired when the value of the input changes @props.onSearch - REQUIRED - [Function] Function that is fired when the timer triggers after the set searchInterval @props.className - OPTIONAL - [String] CSS class applied to form wrapper @props.placeholder - OPTIONAL - [String | Number] Default placeholder value of text input @props.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. This passes the result object and close method callback @props.maxContainerHeight - OPTIONAL - [Number | String] The maximum height of the typeahead results container. The resultConfig.height property should be evenly divisible into maxContainerHeight @props.validation - OPTIONAL - [Function] a method that takes the value and returns an arry of validation objects always return an empty array for a valid value see the validation store for more documentation on validation objects @props.zIndex - OPTIONAL - [Number] Default style is 10. Optionally pass higher number to cover typeaheads on the same page. @props.disabled - OPTIONAL - [Boolean] @props.clearInput - OPTIONAL - [Function] a method that will clear out the input if passed display the clear btn in the input field & */ InputTypeAhead = createClass({ displayName: 'InputTypeAhead', propTypes: { resultConfig: PropTypes.shape({ component: PropTypes.func.isRequired, height: PropTypes.number.isRequired, right: PropTypes.number, left: PropTypes.number, onResultSelect: PropTypes.func.isRequired }), onSearch: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, className: PropTypes.string, focusOnMount: PropTypes.bool, results: PropTypes.array.isRequired, minLength: PropTypes.number, value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, maxContainerHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), placeholder: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), searchInterval: PropTypes.number, zIndex: PropTypes.number, disabled: PropTypes.bool }, 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, focusOnMount: false, disabled: false }; }, 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 (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) { return this.props.resultConfig.onResultSelect(result, 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(value, jsonPath) { if (jsonPath == null) { jsonPath = null; } if (jsonPath) { this.props.onChange(value, jsonPath); } else { this.props.onChange(value); } return this.executeSearch(value); }, executeSearch: function(term) { var length, minLength, onSearch, ref2, searchInterval; ref2 = this.props, onSearch = ref2.onSearch, minLength = ref2.minLength, searchInterval = ref2.searchInterval; length = term.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(term) : 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 }); }, render: function() { var className, clearInput, disabled, focusOnMount, headerComponent, i, index, isInPopover, jsonPath, len, loading, maxLength, noResults, onKeyDown, placeholder, ref2, ref3, result, resultConfig, resultItems, results, selectedResultIndex, showNoResults, showResults, tabIndex, validation, value, zIndex; ref2 = this.props, results = ref2.results, className = ref2.className, headerComponent = ref2.headerComponent, placeholder = ref2.placeholder, showNoResults = ref2.showNoResults, resultConfig = ref2.resultConfig, focusOnMount = ref2.focusOnMount, onKeyDown = ref2.onKeyDown, validation = ref2.validation, isInPopover = ref2.isInPopover, zIndex = ref2.zIndex, disabled = ref2.disabled, clearInput = ref2.clearInput, maxLength = ref2.maxLength, jsonPath = ref2.jsonPath, tabIndex = ref2.tabIndex; 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, style: zIndex != null ? { zIndex: zIndex } : void 0 }, [ TextInput({ key: 'input', ref: 'input', autoComplete: false, value: value, focusOnMount: focusOnMount, placeholder: placeholder, loading: loading, onChange: this.handleChange, onKeyDown: onKeyDown, className: resultItems.length && !loading ? 'no-radius' : void 0, validation: validation, isInPopover: isInPopover, disabled: disabled, maxLength: maxLength, jsonPath: jsonPath, tabIndex: tabIndex }), clearInput != null ? button({ className: 'search-clear', title: 'Clear Input', key: 'inputClearBtn', onClick: clearInput, tabIndex: -1 }, []) : void 0, resultItems.length && !loading && (headerComponent != null) ? headerComponent({ key: 'header' }) : void 0, !loading && showResults ? div({ key: 'results', ref: 'results', className: 'type-ahead-results', style: { height: this.calculateMaxVisibleResults() * resultConfig.height + 2, right: resultConfig.right != null ? resultConfig.right + "px" : void 0, left: resultConfig.left != null ? resultConfig.left + "px" : void 0 } }, [ 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);