UNPKG

ldx-widgets

Version:

widgets

441 lines (413 loc) 15.7 kB
(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);