ldx-widgets
Version:
widgets
441 lines (413 loc) • 15.7 kB
JavaScript
(function() {
var ESCAPE, MultiSelect, MultiSelectOption, React, ReactDOM, SearchInput, _, button, div, li, makeGuid, ref1, ul;
React = require('react');
ReactDOM = require('react-dom');
_ = require('lodash');
MultiSelectOption = React.createFactory(require('./multi_select_option'));
SearchInput = React.createFactory(require('./search_input'));
makeGuid = require('../utils').makeGuid;
ESCAPE = require('../constants/keyboard').ESCAPE;
ref1 = React.DOM, div = ref1.div, button = ref1.button, ul = ref1.ul, li = ref1.li;
/*
Multi Select Props
@props.options - REQUIRED - Array
An array of options for the user to click - can be a flat array (where each entry is both the value and label)
or an array of objects, where the value is the `valueField` and the label is the `labelField` (see below)
@props.values - OPTIONAL - Array
A flat array of values that the control initializes to. If the options array is objects, each entry should be the
`valueField` value. Will default to an empty array
@props.labelField - OPTIONAL - String
The attribute name from each option object that is the 'value`
@props.valueField - OPTIONAL - String
The attribute name from each option object that is the user facing label
@props.onChange - OPTIONAL - function
Function/method to fire when the data changes
@props.filter - OPTIONAL - Boolean - default: 'auto'
Show text filter? auto will show it whenever the options list is longer than 4
@props.allowDefault - OPTIONAL - Boolean - default: false
Allow one entry to be set as the default
@props.valueOfDefault - OPTIONAL - used in conjunction w/ allowDefault
The value of the option that is currently set as the default
@props.searchPlaceholder - OPTIONAL
placeholder text for the add button
@props.editPlaceholder - OPTIONAL
placeholder text for the filter field
@props.tabIndex - OPTIONAL
tab order of the edi button
@props.returnFullObjects - OPTIONAL - Boolean - default: false
whether or not the getFormData method should return a collection of selected objects or a flat array
@props.onRemove - OPTIONAL - function
function call when an item is removed, will pass the removed item
*/
MultiSelect = React.createClass({
displayName: 'MultiSelect',
contextTypes: {
clearValidationError: React.PropTypes.func,
addValidationError: React.PropTypes.func,
getValidationStatus: React.PropTypes.func,
toggleValidationError: React.PropTypes.func
},
getDefaultProps: function() {
return {
filter: 'auto',
allowDefault: false,
editPlaceholder: 'Edit Items',
searchPlaceholder: 'Filter Items',
tabIndex: -1,
returnFullObjects: false,
isInPopover: false,
onRemove: function() {}
};
},
render: function() {
var allowDefault, buttonText, disabled, editPlaceholder, error, filter, filterTerm, forceShowAllErrors, getValidationStatus, i, isActive, isValid, j, k, len, len1, notSelected, option, otherOptions, outerClass, ref2, ref3, ref4, searchPlaceholder, selected, selectedOptions, tabIndex, theDefault, valueHasChanged, visible;
ref2 = this.state, selected = ref2.selected, notSelected = ref2.notSelected, isActive = ref2.isActive, theDefault = ref2.theDefault, filterTerm = ref2.filterTerm, valueHasChanged = ref2.valueHasChanged;
ref3 = this.props, editPlaceholder = ref3.editPlaceholder, searchPlaceholder = ref3.searchPlaceholder, allowDefault = ref3.allowDefault, filter = ref3.filter, tabIndex = ref3.tabIndex, disabled = ref3.disabled;
getValidationStatus = this.context.getValidationStatus;
ref4 = getValidationStatus(this.inputId), error = ref4.error, forceShowAllErrors = ref4.forceShowAllErrors;
isValid = error == null;
outerClass = 'multiselect field-wrap';
if (!isValid && (valueHasChanged || forceShowAllErrors)) {
outerClass += ' invalid';
}
selectedOptions = [];
otherOptions = [];
for (i = j = 0, len = selected.length; j < len; i = ++j) {
option = selected[i];
selectedOptions.push(MultiSelectOption({
key: option.value,
ref: option.value,
option: option,
allowDefault: allowDefault,
isActive: isActive,
setValues: this.setValues,
disabled: disabled,
setDefault: this.setDefault,
tabIndex: tabIndex,
isTheDefault: option === theDefault,
onRemove: this.props.onRemove,
onBlur: i === 0 ? this.btnBlur : null
}));
}
if (selectedOptions.length === 0) {
selectedOptions.push(li({
key: 'none',
className: 'multiselect-none'
}, ['None']));
}
visible = _.filter(notSelected, {
isVisible: true
});
if (isActive) {
for (i = k = 0, len1 = visible.length; k < len1; i = ++k) {
option = visible[i];
otherOptions.push(MultiSelectOption({
key: option.value,
ref: option.value,
option: option,
allowDefault: allowDefault,
isActive: isActive,
tabIndex: tabIndex,
setValues: this.setValues,
onBlur: i === visible.length - 1 ? this.btnBlur : null
}));
}
}
buttonText = "+ " + editPlaceholder;
return div({
className: outerClass,
onClick: this.toggleOn
}, [
ul({
key: 'selectedList',
ref: 'selectedList',
className: 'multiselect-list-in'
}, selectedOptions), this.filterShouldBeShown() ? SearchInput({
ref: 'filterField',
key: 'filter-input',
placeholder: searchPlaceholder,
handleChange: this.handlefilter,
wrapClass: 'multi-select-filter',
width: '100%',
focusOnMount: true,
disabled: disabled,
tabIndex: tabIndex,
term: filterTerm
}) : void 0, !(isActive || disabled) ? button({
key: 'addButton',
ref: 'addButton',
className: 'multiselect-edit',
tabIndex: tabIndex
}, buttonText) : void 0, isActive ? ul({
key: 'notSelectedList',
ref: 'notSelectedList',
className: 'multiselect-list-out'
}, otherOptions) : void 0, div({
key: 'errors',
className: 'field-errors-show',
ref: 'errorAnchor',
onMouseOver: this.handleErrorMouseOver,
onMouseOut: this.handleErrorMouseOut
})
]);
},
componentWillMount: function() {
return this.inputId = makeGuid();
},
componentDidMount: function() {
var selected, validation;
document.addEventListener('keydown', this.handleKeyDown);
document.addEventListener('click', this.blur, false);
selected = this.state.selected;
validation = this.props.validation;
return this.validate(validation, selected);
},
componentWillReceiveProps: function(nextProps) {
var ref2, selected, validation, validationChanged;
validation = nextProps.validation;
selected = this.state.selected;
validationChanged = (typeof validation === 'function' ? false : !_.isEqual(validation != null ? validation.messages : void 0, (ref2 = this.props.validation) != null ? ref2.messages : void 0));
if (validationChanged) {
return this.validate(validation, selected);
}
},
componentWillUnmount: function() {
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('click', this.blur, false);
return this.context.clearValidationError(this.inputId);
},
getInitialState: function() {
var allowDefault, j, labelField, len, newOption, option, options, ref2, selected, theDefault, valueField, valueOfDefault, values;
ref2 = this.props, options = ref2.options, values = ref2.values, labelField = ref2.labelField, valueField = ref2.valueField, valueOfDefault = ref2.valueOfDefault, allowDefault = ref2.allowDefault;
this.allOptions = [];
for (j = 0, len = options.length; j < len; j++) {
option = options[j];
newOption = {
isSelected: false,
isVisible: true
};
if (typeof option === 'object') {
if (!(((valueField != null) && (labelField != null)) || ((option.value != null) && (options.label != null)))) {
return typeof console !== "undefined" && console !== null ? console.error('MultiSelect requires labelField and valueField props when the options array is made up of objects') : void 0;
}
newOption.value = option[valueField];
newOption.label = option[labelField];
} else {
newOption.label = option.toString();
newOption.value = option;
}
if ((values != null) && values.indexOf(newOption.value) !== -1) {
newOption.isSelected = true;
}
this.allOptions.push(newOption);
theDefault = _.find(this.allOptions, {
value: valueOfDefault
});
selected = _.filter(this.allOptions, {
isSelected: true
});
if (theDefault == null) {
if (selected.length != null) {
theDefault = selected[0];
}
}
}
return {
selected: selected || [],
notSelected: _.filter(this.allOptions, {
isSelected: false
}) || [],
isActive: false,
theDefault: theDefault,
filterTerm: '',
valueHasChanged: false
};
},
getValue: function() {
return this.getFormData();
},
getFormData: function() {
var j, len, option, ref2, results, selectedValues;
selectedValues = _.map(_.filter(this.allOptions, {
isSelected: true
}), 'value');
if (!this.props.returnFullObjects) {
return selectedValues;
}
ref2 = this.props.options;
results = [];
for (j = 0, len = ref2.length; j < len; j++) {
option = ref2[j];
if (selectedValues.indexOf(option[this.props.valueField]) !== -1) {
results.push(option);
}
}
return results;
},
toggleOn: function(e) {
var base;
if (this.props.disabled) {
return;
}
if (typeof (base = e.nativeEvent).stopImmediatePropagation === "function") {
base.stopImmediatePropagation();
}
if (!this.state.isActive) {
return this.setState({
isActive: true
}, (function(_this) {
return function() {
if (!_this.filterShouldBeShown()) {
return _this.focusFirstOption();
}
};
})(this));
}
},
focusFirstOption: function() {
var firstSelected, firstUnselected;
firstUnselected = this.refs.notSelectedList.getElementsByTagName('button')[0];
firstSelected = this.refs.selectedList.getElementsByTagName('button')[0];
if (firstUnselected != null) {
return firstUnselected.focus();
} else {
return firstSelected.focus();
}
},
btnBlur: function() {
var ref, ref2, ref3, refname;
ref2 = this.refs;
for (refname in ref2) {
ref = ref2[refname];
if (refname === 'addButton') {
continue;
}
if (((ref3 = ref.refs) != null ? ref3.toggleBtn : void 0) === document.activeElement) {
return;
}
if (ref === document.activeElement) {
return;
}
}
return this.blur();
},
blur: function(cb) {
return this.setState({
isActive: false
}, function() {
return typeof cb === "function" ? cb() : void 0;
});
},
setDefault: function(newDefault) {
return this.setState({
theDefault: newDefault
});
},
setValues: function(option) {
var selected, theDefault, validation;
theDefault = this.state.theDefault;
validation = this.props.validation;
option.isSelected = !option.isSelected;
selected = _.filter(this.allOptions, {
isSelected: true
});
if (selected.indexOf(theDefault) === -1) {
if (selected.length) {
theDefault = selected[0];
}
}
return this.setState({
selected: _.filter(this.allOptions, {
isSelected: true
}),
notSelected: _.filter(this.allOptions, {
isSelected: false
}),
theDefault: theDefault,
valueHasChanged: true
}, function() {
var base;
if (this.filterShouldBeShown()) {
this.refs.filterField.focus();
} else {
this.focusFirstOption();
}
if (typeof (base = this.props).onChange === "function") {
base.onChange();
}
return this.validate(validation, this.state.selected);
});
},
handlefilter: function(term) {
var item, j, k, len, len1, notSelected, ref2;
notSelected = _.filter(this.allOptions, {
isSelected: false
});
if (term === '') {
ref2 = this.allOptions;
for (j = 0, len = ref2.length; j < len; j++) {
item = ref2[j];
item.isVisible = true;
}
} else {
for (k = 0, len1 = notSelected.length; k < len1; k++) {
item = notSelected[k];
if (item.label.toLowerCase().search(term.toLowerCase()) !== -1) {
item.isVisible = true;
} else {
item.isVisible = false;
}
}
}
return this.setState({
filterTerm: term,
notSelected: notSelected
});
},
filterShouldBeShown: function() {
return this.state.isActive && (this.props.filter === true || (this.props.filter === 'auto' && this.allOptions.length > 4));
},
validate: function(validation, value) {
var addValidationError, clearValidationError, isInPopover, ref2, validationError;
if (validation === false) {
return;
}
if (typeof validation === 'function') {
validationError = validation(value);
} else {
validationError = validation;
}
isInPopover = this.props.isInPopover;
ref2 = this.context, addValidationError = ref2.addValidationError, clearValidationError = ref2.clearValidationError;
if (validationError != null) {
return addValidationError(this.refs.errorAnchor, validationError, this.inputId, isInPopover);
} else {
return clearValidationError(this.inputId, isInPopover);
}
},
handleErrorMouseOver: function() {
var isInPopover, toggleValidationError;
isInPopover = this.props.isInPopover;
toggleValidationError = this.context.toggleValidationError;
return toggleValidationError(this.inputId, true, isInPopover);
},
handleErrorMouseOut: function() {
var isInPopover, toggleValidationError;
isInPopover = this.props.isInPopover;
toggleValidationError = this.context.toggleValidationError;
return toggleValidationError(this.inputId, false, isInPopover);
},
handleKeyDown: function(e) {
if (e.keyCode === ESCAPE) {
if (this.state.isActive) {
return this.blur((function(_this) {
return function() {
return _this.refs.addButton.focus();
};
})(this));
}
}
}
});
module.exports = MultiSelect;
}).call(this);