ldx-widgets
Version:
widgets
270 lines (248 loc) • 9.31 kB
JavaScript
(function() {
var DOWN_ARROW, ENTER, ESCAPE, PropTypes, React, SelectInputCustomOptions, TAB, TextInput, UP_ARROW, _, createClass, div, li, ref, ref1, ul;
React = require('react');
createClass = require('create-react-class');
PropTypes = require('prop-types');
TextInput = React.createFactory(require('./text_input_2'));
ref = require('../constants/keyboard'), DOWN_ARROW = ref.DOWN_ARROW, UP_ARROW = ref.UP_ARROW, TAB = ref.TAB, ESCAPE = ref.ESCAPE, ENTER = ref.ENTER;
_ = require('lodash');
ref1 = React.DOM, div = ref1.div, ul = ref1.ul, li = ref1.li;
/*&
@general
Filter select options component. This component lives on the overlay layer, and requires integrated context methods closeOverlay and openOverlay within the application.
@props.filter - [String] - Optional
Initialize the component overlay with a filter value. This will start filtering option labels based on this value.
@props.placeholder - [String] - Optional
Placeholder value for the filter input
@props.onChange - [Function] - Required
Function that is fired when a selection change is made
@props.options - [Array] - Optional
Array of options to render in the select
@props.optionHeight - [Number] - Optional
The fixed height of each menu option
@props.value - [String|Number] - Optional
The value of the currently selected option object
@props.noResultsText - [String] - Optional
Text displayed in the menu when no results match the filter input value
@props.SelectEl - [Function] - Optional
Reference to the select menu component that opens this overlay. If provided, focus will be directed back to the input when closing the overlay
@props.onChangeFilter - [Function] - Optional
Function fired when the filter input changes
&
*/
SelectInputCustomOptions = createClass({
displayName: 'SelectInputCustomOptions',
contextTypes: {
closeOverlay: PropTypes.func
},
propTypes: {
filter: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func.isRequired,
options: PropTypes.array,
optionHeight: PropTypes.number,
value: PropTypes.string,
noResultsText: PropTypes.string,
SelectEl: PropTypes.object,
valueField: PropTypes.string,
labelField: PropTypes.string,
isFilter: PropTypes.bool
},
getDefaultProps: function() {
return {
filter: '',
optionHeight: 20,
noResultsText: 'No results found',
isFilter: false
};
},
getInitialState: function() {
return {
filterValue: '',
focusedOptionIndex: 0,
options: this.props.options
};
},
componentDidMount: function() {
var filter;
filter = this.props.filter;
if (filter) {
this.setState({
filterValue: filter
});
}
if (this.textInput != null) {
this.textInput.focus();
}
return window.addEventListener('keydown', this.handleKeyDown);
},
componentWillUnmount: function() {
return window.removeEventListener('keydown', this.handleKeyDown);
},
componentWillUpdate: function(nextProps, nextState) {
var filterValue, labelField, options, opts;
filterValue = nextState.filterValue;
options = nextProps.options, labelField = nextProps.labelField;
if ((filterValue != null) && this.state.filterValue !== filterValue) {
opts = _.filter(options, (function(_this) {
return function(o) {
return o[labelField].toLowerCase().search(filterValue.toLowerCase()) > -1;
};
})(this));
return this.setState({
options: opts
});
}
},
render: function() {
var filterValue, isFilter, noResultsText, optionListClass, options, placeholder, ref2, ref3, selectOptions, value;
ref2 = this.state, filterValue = ref2.filterValue, options = ref2.options;
ref3 = this.props, placeholder = ref3.placeholder, value = ref3.value, noResultsText = ref3.noResultsText, isFilter = ref3.isFilter;
selectOptions = [];
optionListClass = isFilter ? 'options-list' : 'options-list no-filter';
options.forEach((function(_this) {
return function(o, i) {
return selectOptions.push(_this.processOption(o, i));
};
})(this));
return div({
className: "select-options",
onClick: this.handleOptionsClick
}, [
isFilter ? TextInput({
key: 'input',
ref: (function(_this) {
return function(input) {
return _this.textInput = input;
};
})(this),
id: "filter",
value: filterValue,
onChange: this.handleFilterChange,
placeholder: placeholder
}) : void 0, selectOptions.length ? ul({
key: 'options',
className: optionListClass,
ref: (function(_this) {
return function(optionsList) {
return _this.optionsList = optionsList;
};
})(this),
onScroll: this.handleScroll
}, selectOptions) : div({
key: 'no-results',
className: 'no-results'
}, noResultsText)
]);
},
processOption: function(opt, index) {
var focusedOptionIndex, labelField, optionClass, optionHeight, options, ref2, ref3, value, valueField;
ref2 = this.state, options = ref2.options, focusedOptionIndex = ref2.focusedOptionIndex;
ref3 = this.props, value = ref3.value, optionHeight = ref3.optionHeight, labelField = ref3.labelField, valueField = ref3.valueField;
optionClass = "option";
if (options[focusedOptionIndex] === opt) {
optionClass += " is-focused";
}
if (value === opt[valueField]) {
optionClass += " is-selected";
}
return li({
key: index,
onClick: this.handleClick.bind(this, opt),
className: optionClass,
title: opt[labelField],
style: {
height: optionHeight,
lineHeight: optionHeight + "px"
}
}, opt[labelField]);
},
handleKeyDown: function(e) {
var SelectEl, adjust, currentOption, focusedOptionIndex, isFilter, labelField, newIndex, options, ref2, ref3, valueField;
ref2 = this.state, options = ref2.options, focusedOptionIndex = ref2.focusedOptionIndex, isFilter = ref2.isFilter;
ref3 = this.props, SelectEl = ref3.SelectEl, labelField = ref3.labelField, valueField = ref3.valueField;
adjust = 0;
switch (e.keyCode) {
case UP_ARROW:
e.preventDefault();
adjust = -1;
break;
case DOWN_ARROW:
e.preventDefault();
adjust = 1;
break;
case TAB:
e.preventDefault();
return this.context.closeOverlay(SelectEl);
case ESCAPE:
e.preventDefault();
return this.context.closeOverlay(SelectEl);
case ENTER:
e.preventDefault();
currentOption = options[focusedOptionIndex];
if (currentOption != null) {
return this.props.onChange(currentOption[valueField], this.context.closeOverlay);
}
return this.context.closeOverlay(SelectEl);
default:
e.stopPropagation();
break;
}
newIndex = focusedOptionIndex + adjust;
if (newIndex < 0) {
newIndex = 0;
} else if (newIndex >= options.length - 1) {
newIndex = options.length - 1;
}
return this.setState({
focusedOptionIndex: newIndex
}, (function(_this) {
return function() {
if (isFilter) {
_this.textInput.focus();
}
if (_this.optionsList != null) {
return _this.adjustScrollPosition(adjust);
}
};
})(this));
},
adjustScrollPosition: function(adjust) {
var adjustTop, maxHeight, optionHeight, options, scrollAdjust, scrollTop;
optionHeight = this.props.optionHeight;
options = this.state.options;
scrollAdjust = adjust * optionHeight;
scrollTop = this.optionsList.scrollTop;
adjustTop = this.optionsList.scrollTop + scrollAdjust;
maxHeight = optionHeight * options.length;
if (adjustTop < 0) {
adjustTop = 0;
} else if (adjustTop > maxHeight) {
adjustTop = maxHeight;
}
return this.optionsList.scrollTop = adjustTop;
},
handleFilterChange: function(e) {
return this.setState({
filterValue: e
}, (function(_this) {
return function() {
var base;
return typeof (base = _this.props).onChangeFilter === "function" ? base.onChangeFilter(e) : void 0;
};
})(this));
},
handleClick: function(option, e) {
var onChange, ref2, valueField;
ref2 = this.props, valueField = ref2.valueField, onChange = ref2.onChange;
return onChange(option[valueField], this.context.closeOverlay);
},
handleOptionsClick: function(e) {
return e.stopPropagation();
},
handleScroll: function(e) {
return e.stopPropagation();
}
});
module.exports = SelectInputCustomOptions;
}).call(this);