ldx-widgets
Version:
widgets
361 lines (282 loc) • 11.4 kB
text/coffeescript
React = require 'react'
PropTypes = require 'prop-types'
createClass = require 'create-react-class'
assign = require 'lodash/assign'
omit = require 'lodash/omit'
find = require 'lodash/find'
last = require 'lodash/last'
moment = require 'moment'
DatePicker = React.createFactory(require '../date_picker')
SelectInput2 = React.createFactory(require '../select_input_2')
{div, label, span} = require 'react-dom-factories'
###&
@general -
This component passes all props forward to datepicker, see datepicker component for it's props.
@props.formLabel - OPTIONAL - [string]
The value of the label that will display to the left of the input
@props.className - OPTIONAL - [string] - default 'grid grid-pad'
optional class to be added the main div
@props.labelColumnClass - OPTIONAL - [string]
optional class that will be added to the label column
@props.inputColumnClass - OPTIONAL - [string]
optional class that will be added to the input column
@props.isFieldRequired - OPTIONAL - [boolean]
optional boolean that will display the red asterisk if true
@props.fullRow - OPTIONAL - [boolean]
optional boolean that will determine whether to display the input in a full row with or without a label
@props.enableTime - OPTIONAL - [boolean]
includes the time picker
@props.maxDate - OPTIONAL - [moment]
when using the time picker it will disable times of day after this value
@props.minDate - OPTIONAL - [moment]
when using the time picker it will disable times of day before this value
@props.children - OPTIONAL - [array]
optional array of children
&###
GridFormDatePicker = createClass
displayName: 'GridFormDatePicker'
propTypes:
formLabel: PropTypes.string
className: PropTypes.string
labelColumnClass: PropTypes.string
inputColumnClass: PropTypes.string
isFieldRequired: PropTypes.bool
fullRow: PropTypes.bool
enableTime: PropTypes.bool
twentyFourHour: PropTypes.bool
selected: PropTypes.object
minutes: PropTypes.array
AmPm: PropTypes.array
isInPopover: PropTypes.bool
disabled: PropTypes.bool
onChange: PropTypes.func
timeColumns: PropTypes.number
returnDateString: PropTypes.string
maxDate: PropTypes.object
minDate: PropTypes.object
getDefaultProps: ->
labelColumnClass: 'col-3-12'
inputColumnClass: 'col-9-12'
className: 'grid grid-pad'
isFieldRequired: no
fullRow: yes
enableTime: no
twentyFourHour: no
minutes: [0,15,30,45]
AmPm: ['am', 'pm']
isInPopover: no
disabled: no
timeColumns: 2
componentDidMount: -> do @changeOnOutOfRange
componentDidUpdate: -> do @changeOnOutOfRange
changeOnOutOfRange: ->
{selected, onChange, maxDate, minDate} = @props
if selected? and (maxDate? or minDate?)
# If the selected date is out of range, the controls will be updated to a value different than the passed selected date
# In this case onChange must be fired to ensure the parent form has the value displayed in the form
propsValue = @getMomentObject(selected)
controlValue = @getMomentObject(@getValue())
do onChange if not propsValue.isSame(controlValue, 'minute')
render: ->
{formLabel, className, labelColumnClass, inputColumnClass, inputTextClass, isFieldRequired, selected,
fullRow, enableTime, twentyFourHour, tabId, children, isInPopover, tabIndex, disabled, onChange} = @props
if formLabel?
labelClass = 'form-label'
if isFieldRequired then labelClass += ' is-required'
labelField = div {
key: 'label'
className: labelColumnClass
}, label {className: labelClass}, formLabel
# we do not want to pass the className down as it will mess up the styles
inputProps = omit(assign({}, @props, {ref: 'input'}), ['className'])
input = DatePicker inputProps
if enableTime
classes = @getCellClasses(inputColumnClass)
{hour, minute, ampm} = @parseTime(selected)
hourOptions = @getHourOptions {minute, ampm}
inputCell = [
div {
key: 'picker'
className: classes[0]
}, input
div {
key: 'time1'
className: classes[1]
}, [
SelectInput2 {
ref: 'hour'
key: 'hour'
tabId: tabId
wrapperClass: 'hour'
value: hour or ''
options: hourOptions
onChange: onChange
disabled: disabled
isInPopover: isInPopover
tabIndex: tabIndex
}
span {
key: 'divide'
className: 'time-divide'
}, ':'
SelectInput2 {
ref: 'minute'
key: 'minute'
wrapperClass: 'minutes'
tabId: tabId
value: minute or ''
options: @getMinuteOptions {hour, ampm}, hourOptions
onChange: onChange
disabled: disabled
isInPopover: isInPopover
tabIndex: tabIndex
}
SelectInput2 {
ref: 'ampm'
key: 'ampm'
wrapperClass: 'ampm'
tabId: tabId
value: ampm or ''
options: do @getAmPmOptions
onChange: onChange
disabled: disabled
isInPopover: isInPopover
tabIndex: tabIndex
} unless twentyFourHour
]
]
else
inputCell = div {
key: 'input'
className: inputColumnClass
}, input
# This is a full row of a form with a label
if fullRow and formLabel?
content = [
labelField
inputCell
].concat children
div {
className: className
}, content
# This is a full row w/ out a label
else if fullRow
content = [
inputCell
].concat children
div {
className: className
}, content
# This is a single cell within a row
else
inputCell
getValue: ->
{enableTime, returnDateString, twentyFourHour} = @props
timestamp = @refs.input.getValue()
return null unless timestamp?
return timestamp unless enableTime
minute = @refs.minute.getValue()
hour = @refs.hour.getValue()
ampm = @refs.ampm?.getValue() or ''
# When returnDateString is passed the datepicker will return a string instead of a moment object
# in this case momentize it before extracting the time
if returnDateString?
date = moment(timestamp, returnDateString).format('YYYYMMDD')
rv = moment("#{date}#{hour}#{minute}#{ampm}", if twentyFourHour then 'YYYYMMDDHHmm' else 'YYYYMMDDhmma')
rv.format(returnDateString)
else
date = timestamp.format('YYYYMMDD')
moment("#{date}#{hour}#{minute}#{ampm}", if twentyFourHour then 'YYYYMMDDHHmm' else 'YYYYMMDDhmma')
getMomentObject: (date) ->
{returnDateString} = @props
# When returnDateString is passed the datepicker will return a string instead of a moment object
# in this case momentize it before extracting the time
if returnDateString? then moment(date, returnDateString) else date
parseTime: (timestamp) ->
return {} unless timestamp?
{twentyFourHour, returnDateString} = @props
timestamp = @getMomentObject timestamp
{
hour: timestamp.format(if twentyFourHour then 'HH' else 'h')
minute: timestamp.format('mm')
ampm: timestamp.format('a')
}
getHourOptions: (parsedTime)->
{twentyFourHour} = @props
hoursArray =
if twentyFourHour then [0..23]
else [12].concat [1..11]
hourOptions = ({
label: if twentyFourHour then @prependZero hour else "#{hour}"
value: if twentyFourHour then @prependZero hour else "#{hour}"
disabled: no
} for hour in hoursArray)
@removeOutOfRange 'hours', parsedTime, hourOptions
hourOptions
getMinuteOptions: (parsedTime, hourOptions) ->
{minutes} = @props
minuteOptions = ({
label: @prependZero minute
value: @prependZero minute
disabled: no
} for minute in minutes)
@removeOutOfRange 'minutes', parsedTime, minuteOptions, hourOptions
minuteOptions
getAmPmOptions: ->
{AmPm} = @props
ampmOptions = ({
label: opt
value: opt
} for opt in AmPm)
@removeOutOfRangeAmPm ampmOptions
ampmOptions
removeOutOfRange: (timeSection, parsedTime, optionsList, hourOptions = []) ->
{selected, twentyFourHour, minDate, maxDate, minutes} = @props
dateFormat = if twentyFourHour then 'MMDDYYYY HHmm' else 'MMDDYYYY hmma'
if selected?
currentValue = @getMomentObject selected
# If the selected hour is out of range, use the first hour option for checking the mintes
selectedHour = if not find(hourOptions, {value: parsedTime.hour})? then (hourOptions[0]?.value or '') else parsedTime.hour
# When checking for the hours to include in the list, use the highest/lowest minute option
# This will prevent and hour option from appearing that has no corresponding minute options
# It will also ensure the max hours is in the list if there is a corresponding minute that puts it in range
lastMinute = @prependZero last(minutes)
firstMinute = @prependZero minutes[0]
# When the max date is selected, remove hours and minutes that fall after the max time
if maxDate? and currentValue.isSame(maxDate, 'day')
maxDay = maxDate.format('MMDDYYYY')
for option, o in optionsList by -1
checkTime = switch timeSection
when 'hours' then moment("#{maxDay} #{option.value}#{firstMinute}#{if twentyFourHour then '' else parsedTime.ampm}", dateFormat)
when 'minutes' then moment("#{maxDay} #{selectedHour}#{option.value}#{if twentyFourHour then '' else parsedTime.ampm}", dateFormat)
optionsList.splice(o, 1) if checkTime.isAfter(maxDate, 'minute')
# When the min date is selected, remove hours and minutes that fall before the min time
if minDate? and currentValue.isSame(minDate, 'day')
minDay = minDate.format('MMDDYYYY')
for option, o in optionsList by -1
checkTime = switch timeSection
when 'hours' then moment("#{minDay} #{option.value}#{lastMinute}#{if twentyFourHour then '' else parsedTime.ampm}", dateFormat)
when 'minutes' then moment("#{minDay} #{selectedHour}#{option.value}#{if twentyFourHour then '' else parsedTime.ampm}", dateFormat)
optionsList.splice(o, 1) if checkTime.isBefore(minDate, 'minute')
return optionsList
removeOutOfRangeAmPm: (optionsList) ->
{selected, minDate, maxDate} = @props
if selected?
currentValue = @getMomentObject selected
if maxDate?
if currentValue.isSame(maxDate, 'day')
if maxDate.format('a') is 'am' then optionsList.splice 1, 1
if minDate?
if currentValue.isSame(minDate, 'day')
if minDate.format('a') is 'pm' then optionsList.splice 0, 1
getCellClasses: (inputColumnClass) ->
{timeColumns} = @props
classSplit = inputColumnClass.split('-')
dateCol = classSplit[1]
colCount = classSplit[2]
[
"col-#{dateCol - timeColumns}-#{colCount}"
"col-#{timeColumns}-#{colCount}"
]
prependZero: (value) -> if +value < 10 then "0#{value}" else "#{value}"
module.exports = GridFormDatePicker