ldx-widgets
Version:
widgets
256 lines (207 loc) • 6.92 kB
text/coffeescript
React = require 'react'
createClass = require 'create-react-class'
PropTypes = require 'prop-types'
_ = require 'lodash'
{ DOWN_ARROW, RIGHT_ARROW, LEFT_ARROW, UP_ARROW, ENTER, SPACE, ESCAPE, TAB } = require '../constants/keyboard'
{alphaNumericKeyCode} = require '../utils'
FormValidation = require '../mixins/form_validation'
CircleXButton = React.createFactory(require './circle_x_button')
SelectInputCustomOptions = React.createFactory(require './select_input_custom_options')
{div, select, option} = React.DOM
###&
@general
Filterable select menu. This component lives on the overlay layer, and requires integrated context methods closeOverlay and openOverlay within the application.
@props.options - [Array] - Required
Full list of options to display on component
@props.value - [String|Object] - Optional
The value that corresponds to the option object with the matching value
@props.selectText - [String] - Optional
Text displayed as the default value
@props.onChange - [Function] - Required
Function fired when a change is made to the selection
@props.tabIndex - [Number] - Optional
Tab order index
@props.disabled - [Boolean] - Optional
Disabled state of the component
@props.isFilter - [Boolean] - Optional
Show/hide the filter typeahead input. Default is no
@props.returnFullObjects - [Boolean] - Optional
Determine whether `getValue` returns the full option object, or just the default value string
@props.placeholder - [String] - Optional
Placeholder text for the filter input
@props.valueField - [String] - Optional
The name of the key used to reference the value on the option object
@props.labelField - [String] - Optional
The name of the key used to reference the label on the option object
@props.width - [Number] - Optional
The width of the menu popover
@props.height - [Number] - Optional
The height of the menu popover
@props.optionHeight - [Number] - Optional
The fixed height of each menu option
&###
SelectInputCustom = createClass
displayName: 'SelectInputCustom'
mixins: [FormValidation]
contextTypes:
openOverlay: PropTypes.func
closeOverlay: PropTypes.func
propTypes:
options: PropTypes.array.isRequired
selectText: PropTypes.string
onChange: PropTypes.func.isRequired
tabIndex: PropTypes.number
disabled: PropTypes.bool
isFilter: PropTypes.bool
returnFullObjects: PropTypes.bool
placeholder: PropTypes.string
valueField: PropTypes.string
labelField: PropTypes.string
width: PropTypes.number
height: PropTypes.number
optionHeight: PropTypes.number
value: PropTypes.oneOfType [
PropTypes.string
PropTypes.object
]
nibColor: PropTypes.string
getDefaultProps: ->
options: []
selectText: 'Select from list...'
placeholder: 'Filter options'
valueField: 'value'
labelField: 'title'
width: 250
height: 400
isFilter: no
nibColor: 'white'
optionHeight: 20
getInitialState: ->
{value, valueField} = @props
val = ''
if typeof value is 'object' then val = value[valueField]
else val = value
return {
value: val
}
render: ->
{ options, id, selectText, valueField, labelField, tabIndex, disabled, isFilter } = @props
{ value } = @state
optionItems = []
# Populate an initial value
optionItems.push option {
key: "none"
value: ""
}, selectText
options.forEach (o, i) =>
optionItems.push option {
key: i
value: o[valueField]
}, o[labelField]
@getErrors()
roomForCircleXStyle = if value and not disabled then "x-room" else ""
div {
className: "field-wrap filter-select #{@invalidClass} #{roomForCircleXStyle}"
}, [
select {
key: 'select'
ref: (control) => @control = control
tabIndex: tabIndex
id: id
disabled: disabled
value: value
onMouseDown: @handleNativeEvent
onKeyDown: @handleNativeEvent
}, optionItems
CircleXButton {
key: 'clear'
tabIndex: tabIndex
ref: (clearBtn) => @clearBtn = clearBtn
onClick: @clear
} if value and not disabled
div({
className: 'field-errors-show'
key: 'errors'
}, [
div {
className: 'field-errors'
key: 'err'
},
ul {
className: 'field-errors-list'
}, @validationErrors
]) if @validationErrors.length
]
handleNativeEvent: (e) ->
{ keyCode } = e
{ options, selectText, labelField, valueField, height, width, placeholder, isFilter, nibColor, optionHeight } = @props
{ value } = @state
openKeys = [DOWN_ARROW, RIGHT_ARROW, LEFT_ARROW, UP_ARROW, ENTER, SPACE]
closeKeys = [ESCAPE, TAB]
if ((options.length + 1) * optionHeight) < height
height = (options.length + 2) * optionHeight
# Construct the options component
overlay =
activeComponent: 'pvr'
close: @context.closeOverlay
direction: 'below'
element:
key: 'fso'
options: options
onChange: @handleChange
labelField: labelField
valueField: valueField
placeholder: placeholder
value: value
SelectEl: @
isFilter: isFilter
width: width
height: height
anchor: e.currentTarget
noBackdrop: true
nibColor: nibColor
optionHeight: optionHeight
# Handle the keyboard
if keyCode?
isOpenKey = openKeys.indexOf(keyCode) > -1
isCloseKey = closeKeys.indexOf(keyCode) > -1
alphaNum = alphaNumericKeyCode(keyCode)
if isOpenKey or alphaNum
e.preventDefault()
e.stopPropagation()
# For alphanumeric input, start filtering the element
if alphaNum then overlay.element.filter = e.key
# Set the element to the component, replacing the pojo
overlay.element = SelectInputCustomOptions(overlay.element)
# Open the overlay
@context.openOverlay(overlay)
else if isCloseKey
if keyCode isnt TAB
e.preventDefault()
@context.closeOverlay()
return
# Set the element to the component, replacing the pojo
overlay.element = SelectInputCustomOptions(overlay.element)
# Handle mouse clicks
e.preventDefault()
@context.openOverlay(overlay)
handleChange: (value, cb) ->
@setState
value: value
, =>
@props.onChange(value)
@focus()
cb?()
clear: ->
@handleChange('')
focus: ->
@control.focus();
getValue: ->
{returnFullObjects, valueField} = @props
if returnFullObjects
return _.find @props.options, (o) =>
v = o[valueField]
if typeof v is 'number' then v = v.toString()
return v is @control.value
else return @control.value
module.exports = SelectInputCustom