@bigfishtv/cockpit
Version:
224 lines (201 loc) • 5.69 kB
JavaScript
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { createValue, Fieldset } from '@bigfishtv/react-forms'
import { post, get } from '../../api/xhrUtils'
import { renderComponent } from '../../utils/componentUtils'
import { validationErrors } from 'react-forms-cakephp'
export default class Form extends Component {
static propTypes = {
dataUrl: PropTypes.string,
defaultValue: PropTypes.object,
value: PropTypes.object,
schema: PropTypes.object,
submitUrl: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
onLoad: PropTypes.func,
onBeforeChange: PropTypes.func,
onChange: PropTypes.func,
onBeforeSubmit: PropTypes.func,
onSubmitSuccess: PropTypes.func,
onSubmitError: PropTypes.func,
confirmDirty: PropTypes.bool,
confirmMessage: PropTypes.string,
valueMapper: PropTypes.func,
render: PropTypes.func,
}
static defaultProps = {
confirmDirty: true,
valueMapper: data => data.data || data,
confirmMessage: 'You have unsaved changes! If you leave this page, your changes will be lost!',
}
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
block: PropTypes.func,
}),
}),
}
constructor(props) {
super(props)
this.state = {
data: {},
loading: !!props.dataUrl,
formValue: createValue({
schema: props.schema,
value: props.value || props.defaultValue,
onChange: this.onChange,
}),
dirty: false,
submitStatus: 'ready',
}
if (props.dataUrl) {
this.loadData(props.dataUrl)
}
}
loadData(url) {
get({
url,
callback: (data, responseData) => {
this.setState({
data: responseData,
loading: false,
formValue: createValue({
schema: this.props.schema,
value: this.props.valueMapper(responseData),
onChange: this.onChange,
}),
})
this.props.onLoad && this.props.onLoad(this.props.valueMapper(responseData), responseData)
},
})
}
componentDidMount() {
window.addEventListener('beforeunload', this.confirmUnload)
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.confirmUnload)
this.unblock && this.unblock()
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.props.value) {
this.setState({
dirty: false,
formValue: createValue({
schema: nextProps.schema,
value: nextProps.value,
onChange: this.onChange,
}),
})
}
if (nextProps.dataUrl && nextProps.dataUrl !== this.props.dataUrl) {
this.loadData(nextProps.dataUrl)
}
}
componentDidUpdate() {
this.reactRouterConfirmUnload()
}
reactRouterConfirmUnload() {
// compatibility with react router v4
if (this.context && this.context.router) {
const shouldBlock = this.shouldBlockNavigation()
if (shouldBlock && !this.unblock) {
this.unblock = this.context.router.history.block(this.props.confirmMessage)
} else if (!shouldBlock && this.unblock) {
this.unblock()
this.unblock = null
}
}
}
shouldBlockNavigation() {
return this.props.confirmDirty && this.state.dirty && !window.dontPromptChanges
}
confirmUnload = e => {
if (!this.shouldBlockNavigation()) return
;(e || window.event).returnValue = this.props.confirmMessage //Gecko + IE
return this.props.confirmMessage
}
onChange = nextFormValue => {
if (this.props.onBeforeChange) {
nextFormValue = this.props.onBeforeChange(nextFormValue)
if (nextFormValue === false) {
return
}
}
this.setState({
dirty: true,
formValue: nextFormValue,
submitStatus: this.state.submitStatus != 'busy' ? 'ready' : 'busy',
})
this.props.onChange && this.props.onChange(nextFormValue.value)
}
getSubmitUrl() {
if (typeof this.props.submitUrl == 'function') {
return this.props.submitUrl(this.state.formValue.value)
} else {
return this.props.submitUrl
}
}
handleSubmit = event => {
event.preventDefault()
if (this.state.submitStatus == 'busy') return
if (this.props.onBeforeSubmit && this.props.onBeforeSubmit(this.state.formValue.value) === false) {
return
}
this.setState(
{
submitStatus: 'busy',
},
() => {
post({
quiet: true,
url: this.getSubmitUrl(),
data: this.state.formValue.value,
callback: (data, response) => {
this.onChange(
createValue({
schema: this.props.schema,
value: data,
onChange: this.onChange,
})
)
this.setState({ submitStatus: 'success', dirty: false })
this.props.onSubmitSuccess && this.props.onSubmitSuccess(data, response)
},
callbackError: response => {
if (response.data && response.data.data && response.data.data.errors) {
this.onChange(
createValue({
schema: this.props.schema,
value: this.state.formValue.value,
onChange: this.onChange,
params: { forceShowErrors: true },
errorList: validationErrors(response.data.data.errors),
})
)
}
this.props.onSubmitError && this.props.onSubmitError(response)
this.setState({ submitStatus: 'failed' })
},
})
}
)
}
render() {
const { render } = this.props
const formProps = {
// className,
stylesheet: { Root: 'form' },
formValue: this.state.formValue,
onSubmit: this.handleSubmit,
}
const childProps = {
data: this.state.data,
loading: this.state.loading,
dirty: this.state.dirty,
submitStatus: this.state.submitStatus,
// also send these to child props?
formValue: this.state.formValue,
onSubmit: this.handleSubmit,
}
return <Fieldset {...formProps}>{renderComponent(render, childProps) || this.props.children}</Fieldset>
}
}