1
0
mirror of synced 2024-11-29 00:26:12 +03:00

Fixes form submissions, and a bug that prevented value props from populating forms

This commit is contained in:
Justin Schroeder 2020-02-26 00:21:10 -05:00
parent d7e3859951
commit 839f0936ed
11 changed files with 86 additions and 18 deletions

View File

@ -1776,7 +1776,7 @@ var script$1 = {
this.$emit('submit-raw', submission); this.$emit('submit-raw', submission);
submission.hasValidationErrors() submission.hasValidationErrors()
.then(function (hasErrors) { return hasErrors ? false : submission.values(); }) .then(function (hasErrors) { return hasErrors ? false : submission.values(); })
.then(function (json) { return this$1.$emit('submit', json); }); .then(function (json) { return json !== false ? this$1.$emit('submit', json) : null; });
}, },
showErrors: function showErrors () { showErrors: function showErrors () {
for (var fieldName in this.registry) { for (var fieldName in this.registry) {

View File

@ -1779,7 +1779,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
this.$emit('submit-raw', submission); this.$emit('submit-raw', submission);
submission.hasValidationErrors() submission.hasValidationErrors()
.then(function (hasErrors) { return hasErrors ? false : submission.values(); }) .then(function (hasErrors) { return hasErrors ? false : submission.values(); })
.then(function (json) { return this$1.$emit('submit', json); }); .then(function (json) { return json !== false ? this$1.$emit('submit', json) : null; });
}, },
showErrors: function showErrors () { showErrors: function showErrors () {
for (var fieldName in this.registry) { for (var fieldName in this.registry) {

View File

@ -1782,7 +1782,7 @@
this.$emit('submit-raw', submission); this.$emit('submit-raw', submission);
submission.hasValidationErrors() submission.hasValidationErrors()
.then(function (hasErrors) { return hasErrors ? false : submission.values(); }) .then(function (hasErrors) { return hasErrors ? false : submission.values(); })
.then(function (json) { return this$1.$emit('submit', json); }); .then(function (json) { return json !== false ? this$1.$emit('submit', json) : null; });
}, },
showErrors: function showErrors () { showErrors: function showErrors () {
for (var fieldName in this.registry) { for (var fieldName in this.registry) {

2
dist/snow.css vendored
View File

@ -1,5 +1,5 @@
.formulate-input { .formulate-input {
margin-bottom: 2em; } margin-bottom: 1.5em; }
.formulate-input .formulate-input-label { .formulate-input .formulate-input-label {
display: block; display: block;
line-height: 1.5; line-height: 1.5;

4
dist/snow.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -26,10 +26,10 @@ export default class FormSubmission {
values () { values () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const pending = [] const pending = []
const values = cloneDeep(this.form.internalModelProxy) const values = cloneDeep(this.form.internalFormModelProxy)
for (const key in values) { for (const key in values) {
if (typeof this.form.internalModelProxy[key] === 'object' && this.form.internalModelProxy[key] instanceof FileUpload) { if (typeof this.form.internalFormModelProxy[key] === 'object' && this.form.internalFormModelProxy[key] instanceof FileUpload) {
pending.push(this.form.internalModelProxy[key].upload()) pending.push(this.form.internalFormModelProxy[key].upload())
} }
} }
/** /**

View File

@ -85,11 +85,20 @@ export default {
}, },
register (field, component) { register (field, component) {
this.registry[field] = component this.registry[field] = component
if (!component.$options.propsData.hasOwnProperty('formulateValue') && this.hasFormulateValue && this.formulateValue[field]) { const hasVModelValue = Object.prototype.hasOwnProperty.call(component.$options.propsData, 'formulateValue')
const hasValue = Object.prototype.hasOwnProperty.call(component.$options.propsData, 'value')
if (
!hasVModelValue &&
this.hasFormulateValue &&
this.formulateValue[field]
) {
// In the case that the form is carrying an initial value and the // In the case that the form is carrying an initial value and the
// element is not, set it directly. // element is not, set it directly.
component.context.model = this.formulateValue[field] component.context.model = this.formulateValue[field]
} else if (component.$options.propsData.hasOwnProperty('formulateValue') && !shallowEqualObjects(component.internalModelProxy, this.formulateValue[field])) { } else if (
(hasVModelValue || hasValue) &&
!shallowEqualObjects(component.internalModelProxy, this.formulateValue[field])
) {
this.setFieldValue(field, component.internalModelProxy) this.setFieldValue(field, component.internalModelProxy)
} }
}, },
@ -98,9 +107,9 @@ export default {
this.showErrors() this.showErrors()
const submission = new FormSubmission(this) const submission = new FormSubmission(this)
this.$emit('submit-raw', submission) this.$emit('submit-raw', submission)
submission.hasValidationErrors() return submission.hasValidationErrors()
.then(hasErrors => hasErrors ? false : submission.values()) .then(hasErrors => hasErrors ? false : submission.values())
.then(json => this.$emit('submit', json)) .then(json => json !== false ? this.$emit('submit', json) : null)
}, },
showErrors () { showErrors () {
for (const fieldName in this.registry) { for (const fieldName in this.registry) {

View File

@ -203,7 +203,7 @@ export function cloneDeep (obj) {
const newObj = {} const newObj = {}
for (const key in obj) { for (const key in obj) {
if (obj[key] instanceof FileUpload || isValueType(obj[key])) { if (obj[key] instanceof FileUpload || isValueType(obj[key])) {
newObj[key] = obj newObj[key] = obj[key]
} else { } else {
newObj[key] = cloneDeep(obj[key]) newObj[key] = cloneDeep(obj[key])
} }

View File

@ -61,6 +61,20 @@ describe('FormulateForm', () => {
expect(wrapper.find('input').element.value).toBe('123') expect(wrapper.find('input').element.value).toBe('123')
}) })
it('lets fields set form initial value with value prop', () => {
const wrapper = mount({
data () {
return {
formValues: {}
}
},
template: `<FormulateForm v-model="formValues">
<FormulateInput name="name" value="123" />
</FormulateForm>`
})
expect(wrapper.vm.formValues).toEqual({ name: '123' })
})
it('receives updates to form model when individual fields are edited', () => { it('receives updates to form model when individual fields are edited', () => {
const wrapper = mount({ const wrapper = mount({
data () { data () {
@ -132,7 +146,7 @@ describe('FormulateForm', () => {
}) })
it('it emits an instance of FormSubmission', async () => { it('emits an instance of FormSubmission', async () => {
const wrapper = mount(FormulateForm, { const wrapper = mount(FormulateForm, {
slots: { default: '<FormulateInput type="text" formulate-value="123" name="testinput" />' } slots: { default: '<FormulateInput type="text" formulate-value="123" name="testinput" />' }
}) })
@ -141,7 +155,7 @@ describe('FormulateForm', () => {
expect(wrapper.emitted('submit-raw')[0][0]).toBeInstanceOf(FormSubmission) expect(wrapper.emitted('submit-raw')[0][0]).toBeInstanceOf(FormSubmission)
}) })
it('it resolves hasValidationErrors to true', async () => { it('resolves hasValidationErrors to true', async () => {
const wrapper = mount(FormulateForm, { const wrapper = mount(FormulateForm, {
slots: { default: '<FormulateInput type="text" validation="required" name="testinput" />' } slots: { default: '<FormulateInput type="text" validation="required" name="testinput" />' }
}) })
@ -150,4 +164,15 @@ describe('FormulateForm', () => {
const submission = wrapper.emitted('submit-raw')[0][0] const submission = wrapper.emitted('submit-raw')[0][0]
expect(await submission.hasValidationErrors()).toBe(true) expect(await submission.hasValidationErrors()).toBe(true)
}) })
it('resolves submitted form values to an object', async () => {
const wrapper = mount(FormulateForm, {
slots: { default: '<FormulateInput type="text" validation="required" name="testinput" value="Justin" />' }
})
wrapper.find('form').trigger('submit')
await flushPromises()
const submission = await wrapper.vm.formSubmitted()
await flushPromises()
expect(submission).toEqual({testinput: 'Justin'})
})
}) })

View File

@ -1,5 +1,6 @@
import { parseRules, regexForFormat } from '@/libs/utils' import { parseRules, regexForFormat, cloneDeep, isValueType } from '@/libs/utils'
import rules from '@/libs/rules' import rules from '@/libs/rules'
import FileUpload from '@/FileUpload';
describe('parseRules', () => { describe('parseRules', () => {
it('parses single string rules, returning empty arguments array', () => { it('parses single string rules, returning empty arguments array', () => {
@ -83,3 +84,36 @@ describe('regexForFormat', () => {
it('fails date like YYYY-MM-DD with out of bounds day', () => expect(regexForFormat('YYYY-MM-DD').test('1987-01-32')).toBe(false)) it('fails date like YYYY-MM-DD with out of bounds day', () => expect(regexForFormat('YYYY-MM-DD').test('1987-01-32')).toBe(false))
}) })
describe('isValueType', () => {
it('passes on strings', () => expect(isValueType('hello')).toBe(true))
it('passes on numbers', () => expect(isValueType(123)).toBe(true))
it('passes on booleans', () => expect(isValueType(false)).toBe(true))
it('passes on symbols', () => expect(isValueType(Symbol(123))).toBe(true))
it('passes on null', () => expect(isValueType(null)).toBe(true))
it('passes on undefined', () => expect(isValueType(undefined)).toBe(true))
it('fails on pojo', () => expect(isValueType({})).toBe(false))
it('fails on custom type', () => expect(isValueType(FileUpload)).toBe(false))
})
describe('cloneDeep', () => {
it('basic objects stay the same', () => expect(cloneDeep({ a: 123, b: 'hello' })).toEqual({ a: 123, b: 'hello' }))
it('basic nested objects stay the same', () => {
expect(cloneDeep({ a: 123, b: { c: 'hello-world' } }))
.toEqual({ a: 123, b: { c: 'hello-world' } })
})
it('simple pojo reference types are re-created', () => {
const c = { c: 'hello-world' }
const clone = cloneDeep({ a: 123, b: c })
expect(clone.b === c).toBe(false)
})
})

View File

@ -2,7 +2,7 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
.formulate-input { .formulate-input {
margin-bottom: 2em; margin-bottom: 1.5em;
.formulate-input-label { .formulate-input-label {
display: block; display: block;