ldx-widgets
Version:
widgets
296 lines (239 loc) • 8.86 kB
text/coffeescript
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'
###&
Select popover menu with sub-option capability
.options - [Array] - Required
array of objects containing at minimum a label and value attribute
optionally a subLabel property can be passed
.defaultSelected - [Object|String] - Optional
value of the option selected by default
.close - [Function] - Required
func that closes the popover
.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
.onChange - [Function] - Required
method to call when the non selected option is clicked
.hideSelected - [Boolean] - Optional
when on, the defaultSelected option will be removed from the list
.headerTitle - [String] - Optional
optional title String for popover header
.headerClass - [String] - Optional
optional class for popover header
.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.
.pvrProps - [Object] - Optional
properties germane to PVR wrapper: width, height, anchor, hAdjust, vAdjust, direction
.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
.multiSelectKey - [String|Number] - Optional
`default: 'id'`
a custom property name can be defined for keying multiSelect options.
must be a unique identifier.
.multiSelectGroupId - [String|Number] - Optional
when `multiSelect = true`, selection scoping can be achieved by providing a group ID to separate selection groups.
.footerComponent - [Component] - Optional
Create a footer space at the bottom of the SelectPvr with a passed component
.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} =
{
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} =
= new Map()
if multiSelect
options.forEach (o) =>
{multiSelectGroupId} = o
if .has(multiSelectGroupId)
.set(multiSelectGroupId, .get(multiSelectGroupId) + 1)
else
.set(multiSelectGroupId, 1)
componentWillUnmount: ->
{onChange} =
{selectedOptions} =
if then onChange(selectedOptions)
render: ->
{styleMixin, options, hideSelected, optionHeight, headerTitle, headerClass, noWrapOptions, disabled, className, maxHeight, close, canDeselect, multiSelect, footerComponent, footerHeight} =
{selectedOptions, scale, aggregateOptionsHeight, openSubOptions} =
style = {}
pvrProps = cloneDeep .pvrProps
= headerTitle?
unless pvrProps.height?
pvrProps.height = aggregateOptionsHeight - (if hideSelected then optionHeight else 0)
if
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 =
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:
multiSelect: multiSelect
multiSelectGroupId: chld.multiSelectGroupId
canDeselect: chld.canDeselect
handleChange: chld.handleChange or
noWrapOptions: chld.noWrapOptions
customClass: chld.customClass
}
optionEls.push SelectPvrOption {
hasSubLabel: subLabel?
option: option
optionHeight: optionHeight
key: id or value
isSelected: optionsEqual
canDeselect: canDeselect
handleChange:
noWrapOptions: noWrapOptions
subOptionsHeight: subOptionsHeight
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
div {
key: 'inner-rows'
}, optionEls
footerComponent if footerComponent?
]
Pvr(pvrProps)
handleChange: (option, multiSelectGroupId) ->
{multiSelect, multiSelectKey} =
{selectedOptions} =
= .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
selectedOptions: opt
unless
.onChange opt
.close()
setOpenSubOptions: (option, adjust) ->
{options, optionHeight} =
agg = options.length * optionHeight + (options.length * 1) + adjust
openSubOptions: option
aggregateOptionsHeight: agg
compareOptions: (option) ->
{selectedOptions} =
{multiSelect} =
{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