Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VueForm/components/ConnectedArrayField.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export default {
const nextArray = [
...arrayWithout.slice(0, toIndex),
elementToMove,
...arrayWithout.slice(toIndex + 1),
...arrayWithout.slice(toIndex),
]

this.setValue(nextArray)
Expand Down
252 changes: 35 additions & 217 deletions VueForm/components/Form/Form.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
import {
isNil,
isBoolean,
isEmpty,
isEqual,
has,
mapValues,
union,
merge,
without,
omit,
} from 'lodash'
import Vue from 'vue'
import { isBoolean, isEmpty, isEqual, merge, omit } from 'lodash'
import { Form, Button } from 'element-ui'
import CONSTANTS from '../../constants'
import { validate, asyncValidate } from '../../validators/validate'
import isValid from '../../utils/isValid'
import { VueFormStoreParams } from '../../store'
import FormItem from '../ConnectedFormItem'
import Notification from '../Notification'
import props, { BUTTONS_POSITION } from './props'
Expand All @@ -22,58 +11,25 @@ import styles from './styles'
export default {
props,

data() {
return {
// { [fieldName]: Any }
state: {},
beforeMount() {
this.store = new Vue(VueFormStoreParams)

// { [fieldName]: String }
syncErrors: {},
asyncErrors: {},

// { [fieldName]: Promise }
asyncValidations: {},

// Array<String>
formFields: [],

// Array<String>
removedFields: [],

// { [fieldName]: true }
touchedFields: {},

form: {
submitting: false,
validating: false,
},
}
this.store.setInitialValues(this.initialValues)
this.store.setHandleModelChange(this.handleModelChange)
},

mounted() {
this.defaultInitialValues = this.initialValues
},

updated() {
if (!isEqual(this.defaultInitialValues, this.initialValues)) {
this.defaultInitialValues = this.initialValues
this.reinitializeValues(this.initialValues)
}
watch: {
initialValues(initialValues, prevInitialValues) {
if (!isEqual(initialValues, prevInitialValues)) {
this.store.reinitializeValues(initialValues)
this.handleModelChange(this.store.state)
}
},
},

computed: {
isValid() {
return isValid([this.syncErrors, this.asyncErrors])
},

isDisabled() {
const { submitting, validating } = this.form

return submitting || validating
},

isSubmitButtonDisabled() {
return this.isDisabled || !this.isValid
return this.store.isDisabled || !this.store.isValid
},

submitButtonType() {
Expand All @@ -100,168 +56,53 @@ export default {

methods: {
// Should be called once
[CONSTANTS.SECRET_VUE_FORM_METHOD](name, fieldLevelInitialValue, validators, asyncValidators) {
const vm = this

this.formFields = union(this.formFields, [name])
this.removedFields = without(this.removedFields, name)

const formLevelInitialValue = vm.initialValues[name]
const value = !isNil(formLevelInitialValue) ? formLevelInitialValue : fieldLevelInitialValue

const setError = nextValue => {
if (validators) {
const error = validate(validators, nextValue, name)
const method = error ? vm.$set : vm.$delete

method(vm.syncErrors, name, error)
}
}

const on = {
success: () => vm.$delete(vm.asyncErrors, name),
error: error => vm.$set(vm.asyncErrors, name, error),
}

const setAsyncError = () => {
if (!this.syncErrors[name] && asyncValidators) {
const promise = asyncValidate(asyncValidators, this.state[name], name)
const off = this.manageValidatingState(name, promise)

promise
.then(on.success)
.catch(on.error)
.then(off)

return promise
}

return Promise.resolve()
}

const setValue = nextValue => {
vm.$set(this.state, name, nextValue)

// When control value changes we need to clean async errors
if (this.asyncErrors[name]) {
vm.$delete(this.asyncErrors, name)
}

this.handleModelChange(this.state)

setError(nextValue)
}

if (!has(this.state, name)) {
setValue(value)
}

const cleanFormValue = () => {
vm.$delete(this.state, name)
vm.$delete(this.syncErrors, name)
vm.$delete(this.asyncErrors, name)
vm.$delete(this.touchedFields, name)

this.removedFields = this.removedFields.concat(name)
}

const setTouched = () => {
vm.$set(this.touchedFields, name, true)
}

return {
cleanFormValue,
setError,
setAsyncError,
setTouched,
useState: () => {
const isFieldTouched = vm.touchedFields[name]

return [
// Current value
vm.state[name],

// Value handler
setValue,

// Current error
isFieldTouched && (vm.syncErrors[name] || vm.asyncErrors[name]),

// Touched indicator
isFieldTouched,

// Initial value
this.initialValues[name],
]
},
}
},

manageValidatingState(name, promise) {
this.form.validating = true
this.$set(this.asyncValidations, name, promise)

return () => {
this.form.validating = false
this.$delete(this.asyncValidations, name)
}
},

manageSubmittingState() {
this.form.submitting = true

return () => {
this.form.submitting = false
}
},

manageTouchedFieldsState() {
this.touchedFields = this.formFields.reduce((memo, name) => ({ ...memo, [name]: true }), {})
[CONSTANTS.SECRET_VUE_FORM_METHOD](name, initialValue, validators) {
return this.store.registerFormControl(name, initialValue, validators)
},

handleFormDisabled(errors) {
this.handleDisabled(errors || { ...this.syncErrors, ...this.asyncErrors })
this.handleDisabled(errors || this.store.allErrors)
},

nativeOnSubmit(event) {
event.preventDefault()

// Just do not anything some form process in progress
if (this.form.submitting) {
// Just don't do anything some form process in progress
if (this.store.form.submitting) {
return false
}

const isSubmitButtonClick = event.type === 'click'

this.manageTouchedFieldsState()
this.store.manageTouchedFieldsState()

// Form Level Sync Validate
if (this.validate) {
const syncErrors = this.validate(this.state)
const syncErrors = this.validate(this.store.state)

if (!isEmpty(syncErrors)) {
this.syncErrors = syncErrors
this.store.addFormSyncErrors(syncErrors)

return this.handleFormDisabled(syncErrors)
}

this.syncErrors = {}
}

if (!this.isValid && isSubmitButtonClick) {
if (!this.store.isValid && isSubmitButtonClick) {
return this.handleFormDisabled()
}

const messages = this.messages || {}

const off = this.manageSubmittingState()
const off = this.store.manageSubmittingState()
const submitForm = () =>
Promise.resolve(
this.handleSubmit(merge({}, omit(this.initialValues, this.removedFields), this.state))
this.handleSubmit(
merge({}, omit(this.initialValues, this.store.removedFields), this.store.state)
)
)

const submitPromise = this.form.validating
? Promise.all(Object.values(this.asyncValidations)).then(submitForm)
const submitPromise = this.store.form.validating
? Promise.all(Object.values(this.store.asyncValidations)).then(submitForm)
: submitForm()

// Just subscribe to promise, do not catch errors
Expand All @@ -270,7 +111,6 @@ export default {
() => Notification.success(messages.success),
() => {
Notification.error(messages.error)

this.handleFormDisabled()
}
)
Expand All @@ -282,45 +122,23 @@ export default {
nativeOnReset(event) {
event.preventDefault()

Object.entries(this.state).forEach(([key, value]) => {
const initialValue = this.initialValues[key]
this.store.resetValues()

this.$set(this.state, key, initialValue || (Array.isArray(value) ? [] : ''))
this.$delete(this.syncErrors, key)
this.$delete(this.asyncErrors, key)
this.$delete(this.touchedFields, key)
})

if (this.handleReset) {
this.handleReset(this.initialValues)
if (this.handleCancel) {
this.handleCancel(this.initialValues)
}
},

reinitializeValues(updatedInitialValues) {
this.state = mapValues(this.state, (value, key) => {
if (!isNil(updatedInitialValues[key])) {
return updatedInitialValues[key]
}

return Array.isArray(value) ? [] : undefined
})
this.syncErrors = {}
this.asyncErrors = {}
this.touchedFields = {}

this.handleModelChange(this.state)
},

renderPlainButtons() {
return (
<div style={this.buttonsStyles}>
{this.reset && (
<Button nativeType="reset" disabled={this.isDisabled}>
<Button nativeType="reset" disabled={this.store.isDisabled}>
{this.buttons.reset}
</Button>
)}
{this.save && (
<Button nativeType="submit" type="primary" disabled={this.isDisabled}>
<Button nativeType="submit" type="primary" disabled={this.store.isDisabled}>
{this.buttons.save}
</Button>
)}
Expand Down
2 changes: 1 addition & 1 deletion VueForm/components/Form/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const formProps = {
type: Function,
default: noop,
},
handleReset: Function,
handleCancel: Function,

validate: Function,
asyncValidate: Function,
Expand Down
1 change: 1 addition & 0 deletions VueForm/constants/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export default {
SECRET_VUE_FORM_METHOD: 'REGISTER_VUE_FORM_CONTROL',
VUE_FORM_REINITIALIZE: 'vue-form-reinitialize',
}
Loading