ldx-widgets
Version:
widgets
310 lines (279 loc) • 12 kB
JavaScript
(function() {
var PropTypes, Pvr, React, SelectPvr, SelectPvrOption, assign, cloneDeep, createClass, div, filter, isEqual;
React = require('react');
PropTypes = require('prop-types');
createClass = require('create-react-class');
assign = require('lodash/assign');
cloneDeep = require('lodash/cloneDeep');
filter = require('lodash/filter');
isEqual = require('lodash/isEqual');
Pvr = React.createFactory(require('./pvr'));
SelectPvrOption = React.createFactory(require('./select_pvr_option'));
div = require('react-dom-factories').div;
/*&
@general
Select popover menu with sub-option capability
@props.options - [Array] - Required
array of objects containing at minimum a label and value attribute
optionally a subLabel property can be passed
@props.defaultSelected - [Object|String] - Optional
value of the option selected by default
@props.close - [Function] - Required
func that closes the popover
@props.styleMixin - [Object] - Optional
object containing any style properties to mixin with and/or overrride the defaults
note that width height are passed separately so they can have defaults and auto settings
passing widt/height in this object could cause issues
@props.onChange - [Function] - Required
method to call when the non selected option is clicked
@props.hideSelected - [Boolean] - Optional
when on, the defaultSelected option will be removed from the list
@props.headerTitle - [String] - Optional
optional title String for popover header
@props.headerClass - [String] - Optional
optional class for popover header
@props.maxHeight - [Number] - Optional
the maximum height the popover should be. used to set height on the pvr if this is
lower than the computed height.
@props.pvrProps - [Object] - Optional
properties germane to PVR wrapper: width, height, anchor, hAdjust, vAdjust, direction
@props.multiSelect - [Boolean] - Optional
allows the component to have multiple selections before firing the onChange handler.
when multiSelect is enabled, onChange will be deferred to componentWillUnmount
@props.multiSelectKey - [String|Number] - Optional
`default: 'id'`
a custom property name can be defined for keying multiSelect options.
must be a unique identifier.
@props.multiSelectGroupId - [String|Number] - Optional
when `multiSelect = true`, selection scoping can be achieved by providing a group ID to separate selection groups.
@props.footerComponent - [Component] - Optional
Create a footer space at the bottom of the SelectPvr with a passed component
@props.footerHeight - [Number] - Optional
Set a fixed height to pass as calculated to the Pvr parent component. Used to size the footer space.
&
*/
SelectPvr = createClass({
displayName: 'SelectPvr',
propTypes: {
options: PropTypes.array.isRequired,
styleMixin: PropTypes.object,
headerClass: PropTypes.string,
close: PropTypes.func.isRequired,
optionHeight: PropTypes.number,
onChange: PropTypes.func.isRequired,
maxHeight: PropTypes.number,
pvrProps: PropTypes.object,
canDeselect: PropTypes.bool,
noWrapOptions: PropTypes.bool,
defaultSelected: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
multiSelect: PropTypes.bool,
multiSelectKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
multiSelectGroupId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
footerComponent: PropTypes.object,
footerHeight: PropTypes.number
},
getInitialState: function() {
var defaultSelected, optionHeight, options, ref;
ref = this.props, defaultSelected = ref.defaultSelected, options = ref.options, optionHeight = ref.optionHeight;
return {
selectedOptions: defaultSelected,
aggregateOptionsHeight: options.length * optionHeight + options.length * 1,
openSubOptions: null
};
},
getDefaultProps: function() {
return {
options: [],
styleMixin: {},
headerTitle: null,
headerClass: '',
hideSelected: false,
optionHeight: 36,
noWrapOptions: false,
pvrProps: {},
canDeselect: false,
multiSelect: false,
multiSelectKey: 'id',
footerComponent: null,
footerHeight: 0,
defaultSelected: null
};
},
componentWillMount: function() {
var multiSelect, options, ref;
ref = this.props, multiSelect = ref.multiSelect, options = ref.options;
this.multiSelectGroupIds = new Map();
if (multiSelect) {
return options.forEach((function(_this) {
return function(o) {
var multiSelectGroupId;
multiSelectGroupId = o.multiSelectGroupId;
if (_this.multiSelectGroupIds.has(multiSelectGroupId)) {
return _this.multiSelectGroupIds.set(multiSelectGroupId, _this.multiSelectGroupIds.get(multiSelectGroupId) + 1);
} else {
return _this.multiSelectGroupIds.set(multiSelectGroupId, 1);
}
};
})(this));
}
},
componentWillUnmount: function() {
var onChange, selectedOptions;
onChange = this.props.onChange;
selectedOptions = this.state.selectedOptions;
if (this.changeOnUnmount) {
return onChange(selectedOptions);
}
},
render: function() {
var aggregateOptionsHeight, canDeselect, childItems, children, chld, className, close, disabled, footerComponent, footerHeight, headerClass, headerTitle, hideSelected, i, id, j, label, len, len1, maxHeight, multiSelect, multiSelectGroupId, noWrapOptions, openSubOptions, opth, option, optionEls, optionHeight, options, optionsEqual, pvrProps, ref, ref1, ref2, scale, selectedOptions, style, styleMixin, subLabel, subOptionsHeight, value;
ref = this.props, styleMixin = ref.styleMixin, options = ref.options, hideSelected = ref.hideSelected, optionHeight = ref.optionHeight, headerTitle = ref.headerTitle, headerClass = ref.headerClass, noWrapOptions = ref.noWrapOptions, disabled = ref.disabled, className = ref.className, maxHeight = ref.maxHeight, close = ref.close, canDeselect = ref.canDeselect, multiSelect = ref.multiSelect, footerComponent = ref.footerComponent, footerHeight = ref.footerHeight;
ref1 = this.state, selectedOptions = ref1.selectedOptions, scale = ref1.scale, aggregateOptionsHeight = ref1.aggregateOptionsHeight, openSubOptions = ref1.openSubOptions;
style = {};
pvrProps = cloneDeep(this.props.pvrProps);
this.hasHeader = headerTitle != null;
if (pvrProps.height == null) {
pvrProps.height = aggregateOptionsHeight - (hideSelected ? optionHeight : 0);
}
if (this.hasHeader) {
pvrProps.height += 34;
}
if (footerComponent != null) {
pvrProps.height += footerHeight;
}
if ((maxHeight != null) && pvrProps.height > maxHeight) {
pvrProps.height = maxHeight;
}
assign(style, styleMixin);
if (((ref2 = pvrProps.styleMixin) != null ? ref2.maxHeight : void 0) != null) {
style.maxHeight = pvrProps.styleMixin.maxHeight;
}
style.height = pvrProps.height;
if (pvrProps.width) {
style.width = pvrProps.width;
}
optionEls = [];
for (i = 0, len = options.length; i < len; i++) {
option = options[i];
children = option.children, label = option.label, id = option.id, value = option.value, subLabel = option.subLabel, multiSelectGroupId = option.multiSelectGroupId;
optionsEqual = this.compareOptions(option);
if (hideSelected && optionsEqual) {
continue;
}
childItems = [];
subOptionsHeight = 0;
if (children != null) {
for (j = 0, len1 = children.length; j < len1; j++) {
chld = children[j];
if (!(chld != null)) {
continue;
}
opth = chld.optionHeight || optionHeight;
subOptionsHeight += opth;
childItems.push(SelectPvrOption({
key: chld.id || chld.value,
hasSubLabel: chld.subLabel != null,
option: chld,
optionHeight: opth,
isSelected: this.compareOptions(chld),
multiSelect: multiSelect,
multiSelectGroupId: chld.multiSelectGroupId,
canDeselect: chld.canDeselect,
handleChange: chld.handleChange || this.handleChange,
noWrapOptions: chld.noWrapOptions,
customClass: chld.customClass
}));
}
}
optionEls.push(SelectPvrOption({
hasSubLabel: subLabel != null,
option: option,
optionHeight: optionHeight,
key: id || value,
isSelected: optionsEqual,
canDeselect: canDeselect,
handleChange: this.handleChange,
noWrapOptions: noWrapOptions,
subOptionsHeight: subOptionsHeight,
setOpenSubOptions: this.setOpenSubOptions,
multiSelect: multiSelect,
multiSelectGroupId: multiSelectGroupId,
isOpen: childItems.length && openSubOptions === option
}, childItems));
}
pvrProps.scale = scale;
pvrProps.close = close;
pvrProps.element = div({
key: 'select-pvr',
className: 'select-pvr',
style: style
}, [
this.hasHeader ? div({
key: 'header',
className: "header plain-pvr-content-item " + headerClass
}, headerTitle) : void 0, div({
key: 'inner-rows'
}, optionEls), footerComponent != null ? footerComponent : void 0
]);
return Pvr(pvrProps);
},
handleChange: function(option, multiSelectGroupId) {
var multiSelect, multiSelectKey, opt, ref, ref1, selectedOptions;
ref = this.props, multiSelect = ref.multiSelect, multiSelectKey = ref.multiSelectKey;
selectedOptions = this.state.selectedOptions;
this.changeOnUnmount = this.multiSelectGroupIds.get(multiSelectGroupId) > 1;
opt = {};
if (multiSelect) {
assign(opt, selectedOptions);
if ((multiSelectGroupId != null) && multiSelectGroupId !== ((ref1 = selectedOptions[Object.keys(selectedOptions)[0]]) != null ? ref1.multiSelectGroupId : void 0)) {
opt = {};
}
if (opt[option[multiSelectKey]] != null) {
delete opt[option[multiSelectKey]];
} else {
opt[option[multiSelectKey]] = option;
}
} else {
opt = option;
}
this.setState({
selectedOptions: opt
});
if (!this.changeOnUnmount) {
this.props.onChange(opt);
return this.props.close();
}
},
setOpenSubOptions: function(option, adjust) {
var agg, optionHeight, options, ref;
ref = this.props, options = ref.options, optionHeight = ref.optionHeight;
agg = options.length * optionHeight + (options.length * 1) + adjust;
return this.setState({
openSubOptions: option,
aggregateOptionsHeight: agg
});
},
compareOptions: function(option) {
var k, label, multiSelect, o, selectedOptions;
selectedOptions = this.state.selectedOptions;
multiSelect = this.props.multiSelect;
label = option.label;
if (typeof selectedOptions === 'string') {
return selectedOptions === label;
} else if (typeof selectedOptions === 'object') {
if (multiSelect) {
for (k in selectedOptions) {
o = selectedOptions[k];
if (isEqual(o, option)) {
return true;
}
}
return false;
} else {
return isEqual(selectedOptions, option);
}
}
}
});
module.exports = SelectPvr;
}).call(this);