UNPKG

ldx-widgets

Version:

widgets

296 lines (239 loc) 8.86 kB
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' ###& @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: -> {defaultSelected, options, optionHeight} = @props { selectedOptions: defaultSelected aggregateOptionsHeight: options.length * optionHeight + options.length * 1 openSubOptions: null } getDefaultProps: -> { options: [] styleMixin: {} headerTitle: null headerClass: '' hideSelected: no optionHeight: 36 noWrapOptions: no pvrProps: {} canDeselect: no multiSelect: no multiSelectKey: 'id' footerComponent: null footerHeight: 0 defaultSelected: null } componentWillMount: -> {multiSelect, options} = @props @multiSelectGroupIds = new Map() if multiSelect options.forEach (o) => {multiSelectGroupId} = o if @multiSelectGroupIds.has(multiSelectGroupId) @multiSelectGroupIds.set(multiSelectGroupId, @multiSelectGroupIds.get(multiSelectGroupId) + 1) else @multiSelectGroupIds.set(multiSelectGroupId, 1) componentWillUnmount: -> {onChange} = @props {selectedOptions} = @state if @changeOnUnmount then onChange(selectedOptions) render: -> {styleMixin, options, hideSelected, optionHeight, headerTitle, headerClass, noWrapOptions, disabled, className, maxHeight, close, canDeselect, multiSelect, footerComponent, footerHeight} = @props {selectedOptions, scale, aggregateOptionsHeight, openSubOptions} = @state style = {} pvrProps = cloneDeep @props.pvrProps @hasHeader = headerTitle? unless pvrProps.height? pvrProps.height = aggregateOptionsHeight - (if hideSelected then optionHeight else 0) if @hasHeader pvrProps.height += 34 if footerComponent? pvrProps.height += footerHeight if maxHeight? and pvrProps.height > maxHeight pvrProps.height = maxHeight assign style, styleMixin if pvrProps.styleMixin?.maxHeight? style.maxHeight = pvrProps.styleMixin.maxHeight style.height = pvrProps.height if pvrProps.width style.width = pvrProps.width optionEls = [] for option in options {children, label, id, value, subLabel, multiSelectGroupId} = option optionsEqual = @compareOptions(option) continue if hideSelected and optionsEqual childItems = [] subOptionsHeight = 0 if children? for chld in children when chld? opth = chld.optionHeight or optionHeight subOptionsHeight += opth childItems.push SelectPvrOption { key: chld.id or chld.value hasSubLabel: chld.subLabel? option: chld optionHeight: opth isSelected: @compareOptions(chld) multiSelect: multiSelect multiSelectGroupId: chld.multiSelectGroupId canDeselect: chld.canDeselect handleChange: chld.handleChange or @handleChange noWrapOptions: chld.noWrapOptions customClass: chld.customClass } optionEls.push SelectPvrOption { hasSubLabel: subLabel? option: option optionHeight: optionHeight key: id or value isSelected: optionsEqual canDeselect: canDeselect handleChange: @handleChange noWrapOptions: noWrapOptions subOptionsHeight: subOptionsHeight setOpenSubOptions: @setOpenSubOptions multiSelect: multiSelect multiSelectGroupId: multiSelectGroupId isOpen: childItems.length and openSubOptions is option }, childItems pvrProps.scale = scale pvrProps.close = close pvrProps.element = div { key: 'select-pvr' className: 'select-pvr' style: style }, [ div { key: 'header' className: "header plain-pvr-content-item #{headerClass}" }, headerTitle if @hasHeader div { key: 'inner-rows' }, optionEls footerComponent if footerComponent? ] Pvr(pvrProps) handleChange: (option, multiSelectGroupId) -> {multiSelect, multiSelectKey} = @props {selectedOptions} = @state @changeOnUnmount = @multiSelectGroupIds.get(multiSelectGroupId) > 1 opt = {} # Multi-select if multiSelect assign opt, selectedOptions # Maintain selection by group ids if multiSelectGroupId? and multiSelectGroupId isnt selectedOptions[Object.keys(selectedOptions)[0]]?.multiSelectGroupId opt = {} # De-select it if opt[option[multiSelectKey]]? delete opt[option[multiSelectKey]] # Select it else opt[option[multiSelectKey]] = option # No multi selection else opt = option @setState selectedOptions: opt unless @changeOnUnmount @props.onChange opt @props.close() setOpenSubOptions: (option, adjust) -> {options, optionHeight} = @props agg = options.length * optionHeight + (options.length * 1) + adjust @setState openSubOptions: option aggregateOptionsHeight: agg compareOptions: (option) -> {selectedOptions} = @state {multiSelect} = @props {label} = option if typeof selectedOptions is 'string' return selectedOptions is label else if typeof selectedOptions is 'object' if multiSelect for k, o of selectedOptions if isEqual(o, option) return true return false else return isEqual(selectedOptions, option) module.exports = SelectPvr