ldx-widgets
Version:
widgets
282 lines (261 loc) • 10.6 kB
JavaScript
(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);