UNPKG

ldx-widgets

Version:

widgets

493 lines (460 loc) 17.1 kB
(function() { var ESCAPE, MultiSelect, MultiSelectOption, PropTypes, React, ReactDOM, SearchInput, button, createClass, div, filter, find, isEqual, li, makeGuid, map, ref1, ul; React = require('react'); PropTypes = require('prop-types'); createClass = require('create-react-class'); ReactDOM = require('react-dom'); find = require('lodash/find'); filter = require('lodash/filter'); isEqual = require('lodash/isEqual'); map = require('lodash/map'); MultiSelectOption = React.createFactory(require('./multi_select_option')); SearchInput = React.createFactory(require('./search_input')); makeGuid = require('../utils').makeGuid; ESCAPE = require('../constants/keyboard').ESCAPE; ref1 = require('react-dom-factories'), 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 = createClass({ displayName: 'MultiSelect', contextTypes: { clearValidationError: PropTypes.func, addValidationError: PropTypes.func, getValidationStatus: PropTypes.func, toggleValidationError: 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, 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, 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, stateOptions, 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) { this.validate(validation, selected); } if (nextProps.options !== this.props.options || nextProps.values !== this.props.values) { stateOptions = this.setOptions(nextProps); return this.setState({ selected: stateOptions.selected || [], notSelected: filter(this.allOptions, { isSelected: false }) || [], theDefault: stateOptions.theDefault }); } }, componentWillUnmount: function() { document.removeEventListener('keydown', this.handleKeyDown); document.removeEventListener('click', this.blur, false); return this.context.clearValidationError({ groupId: this.inputId, isInPopover: this.props.isInPopover }); }, getInitialState: function() { var stateOptions; stateOptions = this.setOptions(this.props); return { selected: stateOptions.selected || [], notSelected: filter(this.allOptions, { isSelected: false }) || [], isActive: false, theDefault: stateOptions.theDefault, filterTerm: '', valueHasChanged: false }; }, setOptions: function(params) { var allowDefault, data, j, labelField, len, newOption, obj, option, options, selected, theDefault, valueField, valueOfDefault, values; options = params.options, values = params.values, labelField = params.labelField, valueField = params.valueField, valueOfDefault = params.valueOfDefault, allowDefault = params.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 || find(values, ( obj = {}, obj["" + valueField] = newOption.value, obj )))) { 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]; } } } data = { selected: selected, theDefault: theDefault }; return data; }, 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 jsonPath, onChange, ref2, selected, theDefault, validation; theDefault = this.state.theDefault; ref2 = this.props, validation = ref2.validation, onChange = ref2.onChange, jsonPath = ref2.jsonPath; 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() { if (this.filterShouldBeShown()) { this.refs.filterField.focus(); } else { this.focusFirstOption(); } if (typeof onChange === "function") { onChange(this.getValue(), jsonPath); } 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, ref3, tabId, validationError; if (validation === false) { return; } if (typeof validation === 'function') { validationError = validation(value); } else { validationError = validation; } ref2 = this.props, isInPopover = ref2.isInPopover, tabId = ref2.tabId; ref3 = this.context, addValidationError = ref3.addValidationError, clearValidationError = ref3.clearValidationError; if (validationError != null) { return addValidationError({ anchor: this.refs.errorAnchor, error: validationError, groupId: this.inputId, isInPopover: isInPopover, tabId: tabId }); } else { return clearValidationError({ groupId: this.inputId, isInPopover: isInPopover }); } }, handleErrorMouseOver: function() { var isInPopover, toggleValidationError; isInPopover = this.props.isInPopover; toggleValidationError = this.context.toggleValidationError; return toggleValidationError({ groupId: this.inputId, status: true, isInPopover: isInPopover }); }, handleErrorMouseOut: function() { var isInPopover, toggleValidationError; isInPopover = this.props.isInPopover; toggleValidationError = this.context.toggleValidationError; return toggleValidationError({ groupId: this.inputId, status: false, isInPopover: 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);