ldx-widgets
Version:
widgets
302 lines (246 loc) • 7.7 kB
text/coffeescript
React = require 'react'
createClass = require 'create-react-class'
PropTypes = require 'prop-types'
{StyleSheet, css} = require 'aphrodite/no-important'
assign = require 'lodash/assign'
dialogueMixin = require '../mixins/dialogue_mixin'
{ESCAPE, KEY_S} = require '../constants/keyboard'
ConfirmSave = React.createFactory(require('./confirm_save'))
Spinner = React.createFactory(require('./spinner'))
{div, span, button} = require 'react-dom-factories'
###&
@props.title - OPTIONAL - [String]
title for the modal header
@props.buttons - OPTIONAL - [Array]
Array of button objects with name, handler to be called on click, and disabled boolean, eg...
```
[
{
name: 'Create'
handler: @create
disabled: no
}
]
```
@props.children - REQUIRED - [Element]
React element (or array of elements) to inserted as the form body
@props.styleOverride - OPTIONAL - [Object]
aphrodite style object, optionally can contain .form, .title, .bar, .btn, .class defs, eg...
```
{
formBase: {}
form: {}
title: {}
bar: {}
btn: {}
}
```
@props.canUpdate - OPTIONAL - [Boolean]
Defaults to no, whether or not the user can update and save the form
@props.save - OPTIONAL - [Function]
Function that save the form
@props.close - OPTIONAL - [Function]
Function that closes the form
@props.onClose - OPTIONAL - [Function]
Function that is called right before the modal closes
@props.showClose - OPTIONAL - [Boolean]
Defaults to yes, set it to no to not show the close button
@props.closeAfterSave - OPTIONAL - [Boolean]
Defaults to yes, set it to no to prevent the modal from calling @props.close after a save is complete
Note: in this case you MUST pass an onSaveComplete handler that sets the saveState to null after a save
@props.onSaveComplete - OPTIONAL - [Function]
Function that is called right after the saveState is set to complete and the success indicator finishes animating
@props.closeBtnText - OPTIONAL - [String]
Defaults to 'Cancel', text to display in the close button
@props.loading - OPTIONAL - [Boolean]
Defaults to no, whether or not to show the spinner instead of the children
@props.displayProgressBar - OPTIONAL - [Boolean]
Defaults to no, whether or not the confirm/save show the progress bar instead of the spinner
@props.uploadProgress - OPTIONAL
Progress of a file being uploaded
@props.unSavedMessage - OPTIONAL
Message to be displayed in dialog if you have unsaved changes
&###
Form = createClass
displayName: 'Form'
mixins: [dialogueMixin]
propTypes:
styleOverride: PropTypes.shape
form: PropTypes.object
bar: PropTypes.object
title: PropTypes.object
btn: PropTypes.object
title: PropTypes.string
buttons: PropTypes.array
canUpdate: PropTypes.bool
save: PropTypes.func.isRequired
close: PropTypes.func.isRequired
onClose: PropTypes.func
onSaveComplete: PropTypes.func
showClose: PropTypes.bool
closeAfterSave: PropTypes.bool
closeBtnText: PropTypes.string
unSavedMessage: PropTypes.string
unSavedDialogueHeight: PropTypes.number
unSavedChanges: PropTypes.bool
onSaveFail: PropTypes.func
inLineStyle: PropTypes.object
loading: PropTypes.bool
spinnerProps: PropTypes.object
displayProgressBar: PropTypes.bool
uploadProgress: PropTypes.oneOfType [
PropTypes.string
PropTypes.number
]
getDefaultProps: ->
styleOverride: {}
inLineStyle: {}
buttons: []
canUpdate: true
showClose: yes
closeAfterSave: yes
unSavedDialogueHeight: 100
saveState: null
saveMessage: null
unSavedChanges: no
loading: no
spinnerProps:
length: 7
radius: 7
lines: 12
width: 2
displayProgressBar: no
uploadProgress: ''
componentWillMount: ->
# This is necessary becasue translation is not available on app load
# So this cannot live in default props
@closeBtnText = t 'Cancel'
document.addEventListener 'keydown', @handleKeyPress
componentWillUnmount: ->
document.removeEventListener 'keydown', @handleKeyPress
render: ->
{styleOverride, title, buttons, closeBtnText, showClose, children, save, saveState, saveMessage,
canUpdate, unSavedChanges, onSaveFail, inLineStyle, loading, spinnerProps, displayProgressBar, uploadProgress} = @props
closeBtnText = closeBtnText or @closeBtnText
# buttons
btns = []
# save button
btns.push button {
key: 'save'
className: if unSavedChanges then css(styles.btn) else css(styles.btn, styles.disabled)
onClick: save
disabled: not unSavedChanges
tabIndex: -1
}, t 'Save' if canUpdate
# close/cancel button
btns.push button {
key: 'cancel'
className: css(styles.btn)
onClick: @closeWithCheck
tabIndex: -1
}, closeBtnText if showClose
# other buttons
btns.push button {
key: b.name
className: css(styles.btn)
onClick: b.handler
disabled: b.disabled
}, b.name for b in buttons by -1
assign styles, styleOverride
inLineStyle = assign {}, inLineStyle
assign spinnerProps,
key: 'spinner'
div {
className: css(styles.formBase, styles.form)
style: inLineStyle
}, [
div {
key: 'bar'
className: css(styles.bar)
}, [
div {
key: 'name'
className: css(styles.title)
}, title
btns
] unless loading
@dialogueBox()
ConfirmSave {
key: 'confirm'
done: @saveComplete
fail: -> onSaveFail?()
saveMessage: saveMessage
saveState: saveState
displayProgressBar: displayProgressBar
uploadProgress: uploadProgress
} if saveState?
if loading then Spinner(spinnerProps) else children
]
closeWithCheck: ->
{unSavedMessage, unSavedDialogueHeight, unSavedChanges} = @props
if unSavedChanges
@showDialogue
message: unSavedMessage or t 'There are unsaved changes. How do you want to proceed?'
confirmText: t 'Discard Changes'
height: unSavedDialogueHeight
confirmCallback: @close
else
do @close
close: ->
{close, onClose} = @props
onClose?()
do close
saveComplete: ->
{close, onSaveComplete, closeAfterSave} = @props
# if a onSaveComplete method has been passed then call it
onSaveComplete?()
do close if closeAfterSave
handleKeyPress: (e) ->
{keyCode, metaKey} = e
if keyCode is ESCAPE then do @closeWithCheck
if keyCode is KEY_S and metaKey
do e.preventDefault
{buttons} = @props
if buttons[0]?.name is t 'Save' then do buttons[0].handler
styles = StyleSheet.create
formBase:
position: 'absolute'
backgroundColor: 'white'
overflow: 'hidden'
width: '100%'
height: '100%'
form:
top: '0px'
left: '0px'
bar:
position: 'absolute'
height: '40px'
backgroundColor: '#fafaf6'
borderBottom: '1px solid #e5e4dc'
overflow: 'hidden'
zIndex: '8'
width: '100%'
title:
overflow: 'hidden'
whiteSpace: 'nowrap'
textOverflow: 'ellipsis'
fontSize: '18px'
height: '100%'
lineHeight: '40px'
color: '#666666'
display: 'inline-block'
marginTop: '0px'
marginLeft: '20px'
paddingRight: '20px'
btn:
float: 'right'
marginRight: '15px'
color: '#007fff'
height: '28px'
textAlign: 'center'
lineHeight: '26px'
marginTop: '6px'
fontSize: '13px'
disabled:
color: '#cccccc'
module.exports = Form