Skip to content

Commit

Permalink
feat: add camelizePayloadKeys props
Browse files Browse the repository at this point in the history
  • Loading branch information
14nrv committed Nov 7, 2020
1 parent 91d817c commit 66f24ef
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 30 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

[![Edit vue-form-json-demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/vue-form-json-demo-t97l5?file=/src/main.js)

## Generate a responsive vue form with validation, from [an array](https://github.com/14nrv/vue-form-json/blob/master/src/components/Form/fields.json)
## Generate a vue form with validation from [an array](https://github.com/14nrv/vue-form-json/blob/master/src/components/Form/fields.json)
All fields are required and input text by default.
Once submitted, an event 'formSubmitted' is emitted on $root with the formName and all values.

Expand Down Expand Up @@ -49,8 +49,8 @@ Once submitted, an event 'formSubmitted' is emitted on $root with the formName a

## Install
```sh
yarn add vue-form-json vee-validate bulma @fortawesome/fontawesome-free
#fontawesome is not needed if hasIcon props is false
yarn add vue-form-json
# Optional: bulma @fortawesome/fontawesome-free (fontawesome is not needed if hasIcon props is false)
```

## Usage
Expand Down Expand Up @@ -153,6 +153,10 @@ props: {
hasIcon: {
type: Boolean,
default: true
},
camelizePayloadKeys: {
type: Boolean,
default: false
}
}
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
},
"homepage": "https://github.com/14nrv/vue-form-json#readme",
"dependencies": {
"humps": "^2.0.1",
"ramda": "^0.27.0",
"slugify": "^1.4.4",
"vee-validate": "^3.4.2",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Fields/Checkbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default {
this.value = itemsChecked
itemsChecked.length && (this.$parent.$parent.value = itemsChecked)
itemsChecked && itemsChecked.length && (this.$parent.$parent.value = itemsChecked)
}
}
</script>
Expand Down
5 changes: 3 additions & 2 deletions src/components/Fields/Control.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
:rules="getRules"
:name="item.name || item.label | slugify"
:immediate="!!item.value"
v-slot="{ errors, required, ariaInput }")
v-slot="{ errors, ariaInput }")

.control(:class="{'has-icons-left': item.iconLeft, 'has-icons-right': shouldShowErrorIcon}")
component(v-model.lazy.trim="value",
Expand Down Expand Up @@ -48,7 +48,8 @@ export default {
}),
watch: {
value (val) {
this.$parent.$parent.formValues[this.item.label] = val
const { label, name } = this.item
this.$parent.$parent.formValues[name || label] = val
}
},
computed: {
Expand Down
45 changes: 31 additions & 14 deletions src/components/Form/Form.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import matchers from 'jest-vue-matcher'
import { mount, createLocalVue, createWrapper } from '@vue/test-utils'
import { flatten, pickAll, map } from 'ramda'
import { extendRules, flush, slug } from '@/helpers'
import { camelizeKeys } from 'humps'
import { ValidationProvider, ValidationObserver } from 'vee-validate'
import Form from '@/components/Form'
import fields from './fields'
Expand Down Expand Up @@ -31,21 +32,22 @@ const ZIP_VALUE = '12345'
const PASSWORD_VALUE = ZIP_VALUE
const BIG_VALUE = ZIP_VALUE

const allFields = flatten(fields)
.filter(field => !['html', 'slot'].includes(Object.keys(field)[0]))

const getInitialValue = (label, node, attribute) =>
fields
allFields
.find(field => field.label === label)[node]
.filter(x => x[attribute])
.map(({ value, text }) => value || text)

const COUNTRY_VALUE = getInitialValue('Country', 'options', 'selected')[0]
const CHECKBOX_VALUE = getInitialValue('Checkbox', 'items', 'checked')

const allFields = flatten(fields)
.filter(field => !['html', 'slot'].includes(Object.keys(field)[0]))
const allNormalInputLabel = allFields
.filter(x => !x.type || x.type === 'tel')
.filter(x => !x.validation)
.map(x => x.label)
.map(x => x.name || x.label)

const fieldWithPattern = allFields.find(({ pattern }) => pattern)

Expand Down Expand Up @@ -282,6 +284,13 @@ describe('Form', () => {
expect($inputSubmit).toHaveAttribute('disabled', 'disabled')
})

it('has submit btn enabled if field is not required', async () => {
await wrapper.setProps({ formFields: [{ label: 'a label', isRequired: false }] })
await flush()

expect($inputSubmit).not.toHaveAttribute('disabled', 'disabled')
})

it('enables submit input if all fields are valid', async () => {
await flush()
expect($inputSubmit).toHaveAttribute('disabled', 'disabled')
Expand All @@ -295,17 +304,25 @@ describe('Form', () => {
expect($inputSubmit).toHaveClass('button is-primary')
})

it('sends an event formSubmitted with all values when submit', async () => {
const rootWrapper = createWrapper(wrapper.vm.$root)
it.each([true, false])(
'sends an event formSubmitted with all values when submit with camelizePayloadKeys set to %s',
async camelizePayloadKeys => {
const rootWrapper = createWrapper(wrapper.vm.$root)

await fillForm()
await wrapper.vm.onSubmit()
await wrapper.setProps({ camelizePayloadKeys })
await flush()

expect(rootWrapper).toEmitWith('formSubmitted', {
formName: FORM_NAME,
values: getFormValues()
})
})
await fillForm()
await wrapper.vm.onSubmit()

expect(rootWrapper).toEmitWith('formSubmitted', {
formName: FORM_NAME,
values: camelizePayloadKeys
? camelizeKeys(getFormValues())
: getFormValues()
})
}
)
})

describe('reset', () => {
Expand All @@ -318,7 +335,7 @@ describe('Form', () => {
})

it.each([true, false])(
'can reset value',
'can reset value with resetFormAfterSubmit set to %s',
async hasResetAfterSubmit => {
hasResetAfterSubmit && wrapper.setProps({ resetFormAfterSubmit: true })

Expand Down
38 changes: 28 additions & 10 deletions src/components/Form/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,12 @@ import {
pipe,
map
} from 'ramda'
import { camelizeKeys } from 'humps'
import Label from '@/components/Fields/Label'
import Control from '@/components/Fields/Control'
const getLabels = ({ label }) => label
const getNameOrLabel = ({ label, name }) => name || label
const valueToProp = object => pickAll(object, {})
export default {
Expand Down Expand Up @@ -106,14 +107,18 @@ export default {
hasIcon: {
type: Boolean,
default: true
},
camelizePayloadKeys: {
type: Boolean,
default: false
}
},
data: () => ({
formValues: undefined,
allControls: []
}),
created () {
this.formValues = pipe(flatten, map(getLabels), valueToProp)(this.formFields)
this.formValues = pipe(flatten, map(getNameOrLabel), valueToProp)(this.formFields)
},
mounted () {
this.allControls = this.$refs.control
Expand All @@ -122,29 +127,42 @@ export default {
async onSubmit (ev) {
const isValidated = await this.$refs.form.validate()
isValidated && this.emitValues({
formName: this.formName,
values: this.formValues
})
isValidated && this.resetFormAfterSubmit && this.resetForm(ev)
if (isValidated) {
const valuesFormatted = JSON.parse(JSON.stringify(this.formValues))
this.emitValues({
formName: this.formName,
values: this.camelizePayloadKeys
? camelizeKeys(valuesFormatted)
: valuesFormatted
})
this.resetFormAfterSubmit && this.resetForm(ev)
}
},
emitValues (data) {
this.$root.$emit('formSubmitted', data)
},
clearValues () {
const fieldsWithArrayValue = ['radio, checkbox']
this.allControls.map(x => { x.value = '' })
const subValues = this.allControls.filter(x => x.$children[0].$children[0].value)
subValues.map(x => { x.$children[0].$children[0].value = [] })
subValues.map(x => {
const { type } = x.$children[0].$children[0].item
const hasArrayAsValue = fieldsWithArrayValue.includes(type)
x.$children[0].$children[0].value = hasArrayAsValue ? [] : ''
})
},
clearPrefillValues () {
const inputsPrefilled = this.allControls.filter(x => x.item.value)
inputsPrefilled.map(x => { x.item.value = undefined })
const selects = this.allControls.filter(x => x.item.options)
const selects = this.allControls.filter(x => x.item.type === 'select')
selects.map(select => {
select.item.options.map(option => {
option.selected && (option.selected = false)
option.selected && !option.disabled && (delete option.selected)
})
})
},
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6602,6 +6602,11 @@ humanize-ms@^1.2.1:
dependencies:
ms "^2.0.0"

humps@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa"
integrity sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao=

husky@^4.2.5:
version "4.3.0"
resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.0.tgz#0b2ec1d66424e9219d359e26a51c58ec5278f0de"
Expand Down

0 comments on commit 66f24ef

Please sign in to comment.