Skip to content

Commit

Permalink
feat: Add boostrap4 single and multi error templates.
Browse files Browse the repository at this point in the history
* add bindingProps and bindingActions to default scopedSlots
* add new isValid computed property
* add messages prop to formWrapper. Allows injecting local messages to all singleExtractor fields.
  • Loading branch information
dobromir-hristov committed Jul 17, 2018
1 parent fd1d4d5 commit 39e185d
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 34 deletions.
34 changes: 32 additions & 2 deletions docs-source/form_wrapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ That way fields with the same name like **email** or **name** will get properly
```vue
<template>
<div>
<form-wrapper :validator="$v.form">
<form-wrapper :validator="$v.form" :messages="localMessages">
<form-errors/>
<form-field name="name">
<input type="text" v-model="form.name">
Expand All @@ -46,6 +46,9 @@ export default {
form: {
name: "",
email: ""
},
localMessages: {
email: '{attribute} is not a proper email, you should check it again.'
}
};
},
Expand All @@ -59,12 +62,39 @@ export default {
</script>
```

You have to pass the `validator` prop which is your form object's Vuelidate validation.
Its a good practice to wrap your forms in an object like so:

```js
export default {
data() {
return {
form: {
name: '',
email: ''
}
}
},
validations: {
form: {
name: { required },
email: { required, email }
}
}
}
```
By adding a `messages` prop, you can override the globally defined messages.

::: warning
Does not work in i18n mode for now!
:::

### Single Error Extractor
When using with the `SingleErrorExtractor` component, its required to pass а **name** prop to each error extractor. That is a string representing the field's key in the form.

That way the `singleErrorExtractor` can look into the `formWrapper` validator and get the proper validations.

Using the name prop, the error message `attribute` is guessed from the global `$vuelidateErrorExtractor.attributes` object.
Using the `name` prop, the error message `attribute` is guessed from the global `$vuelidateErrorExtractor.attributes` object.

### Multi Error Extractor

Expand Down
5 changes: 3 additions & 2 deletions src/base-errors-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { getErrorString } from './utils'

export default {
inject: {
formValidator: { default: false }
formValidator: { default: false },
formMessages: { default: () => ({}) }
},
props: {
validator: {
Expand All @@ -25,7 +26,7 @@ export default {
return this.errors.filter(error => error.hasError && error.$dirty)
},
mergedMessages () {
return Object.assign({}, this.$vuelidateErrorExtractor.messages, this.messages)
return Object.assign({}, this.$vuelidateErrorExtractor.messages, this.formMessages, this.messages)
},
firstError () {
return this.activeErrors.length ? this.activeErrors[0] : ''
Expand Down
6 changes: 6 additions & 0 deletions src/single-error-extractor-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export default {
// We are using the Vuelidate keys
return getValidationObject.call(this, key, key, params)
})
},
events () {
return { input: () => this.preferredValidator.$touch() }
},
isValid () {
return this.preferredValidator.$dirty && !this.hasErrors
}
}
}
7 changes: 6 additions & 1 deletion src/templates/form-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ export default {
validator: {
type: Object,
required: true
},
messages: {
type: Object,
default: () => ({})
}
},
render (h) {
return h('div', this.$slots.default)
},
provide () {
return {
formValidator: this.validator
formValidator: this.validator,
formMessages: this.messages
}
}
}
18 changes: 18 additions & 0 deletions src/templates/multi-error-extractor/bootstrap4.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<base-multi-error-extractor v-bind="$attrs" class="was-validated">
<template slot-scope="{ errorMessage }">
<label class="invalid-feedback d-block">{{ errorMessage }}</label>
</template>
</base-multi-error-extractor>
</template>

<script>
import baseMultiErrorExtractor from './baseMultiErrorExtractor.vue'
export default {
inheritAttrs: false,
components: {
baseMultiErrorExtractor
}
}
</script>
2 changes: 2 additions & 0 deletions src/templates/multi-error-extractor/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import baseMultiErrorExtractor from './baseMultiErrorExtractor.vue'
import bootstrap3 from './bootstrap3.vue'
import bootstrap4 from './bootstrap4.vue'
import foundation6 from './foundation6.vue'

export default {
baseMultiErrorExtractor,
bootstrap3,
bootstrap4,
foundation6
}
56 changes: 38 additions & 18 deletions src/templates/single-error-extractor/bootstrap3.vue
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
<template>
<div class="form-group"
:class="{'has-error': hasErrors, 'has-success':(!hasErrors && validator.$dirty)}">
<div
class="form-group"
:class="{'has-error': hasErrors, 'has-success':isValid }">
<slot name="label">
<label class="control-label"
v-if="label">{{ label }} {{ errors ? '*' : '' }}</label>
<label
class="control-label"
v-if="label">
{{ label }} {{ errors ? '*' : '' }}
</label>
</slot>
<slot :errors="activeErrors"
:has-errors="hasErrors"
:first-error-message="firstErrorMessage"
></slot>
<slot name="errors"
:errors="activeErrors"
:has-errors="hasErrors"
:first-error-message="firstErrorMessage">
<slot
:attributes="attributes"
:errors="activeErrors"
:events="events"
:first-error-message="firstErrorMessage"
:has-errors="hasErrors"
:validator="preferredValidator"
/>
<slot
name="errors"
:errors="activeErrors"
:error-messages="activeErrorMessages"
:has-errors="hasErrors"
:first-error-message="firstErrorMessage">
<div class="help-block" v-if="hasErrors">
<span v-if="showSingleError"
:data-validation-attr="firstError.validationKey">
<span
v-if="showSingleError"
:data-validation-attr="firstError.validationKey">
{{ firstErrorMessage }}
</span>
<template v-if="!showSingleError">
<span v-for="error in activeErrors"
:key="error.validationKey"
:data-validation-attr="error.validationKey">
<span
v-for="error in activeErrors"
:key="error.validationKey"
:data-validation-attr="error.validationKey">
{{ getErrorMessage(error.validationKey, error.params) }}
</span>
</template>
Expand All @@ -33,6 +45,14 @@
import singleErrorExtractorMixin from '../../single-error-extractor-mixin'
export default {
mixins: [singleErrorExtractorMixin]
mixins: [singleErrorExtractorMixin],
computed: {
attributes () {
return {
class: { 'form-control': true },
name: this.name || undefined
}
}
}
}
</script>
53 changes: 53 additions & 0 deletions src/templates/single-error-extractor/bootstrap4.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<template>
<div>
<slot name="label">
<label>{{ label }}</label>
</slot>
<slot
:attributes="attributes"
:errors="activeErrors"
:events="events"
:first-error-message="firstErrorMessage"
:has-errors="hasErrors"
:validator="preferredValidator"
/>
<slot
name="errors"
:errors="activeErrors"
:error-messages="activeErrorMessages"
:has-errors="hasErrors"
:first-error-message="firstErrorMessage"
>
<div
:class="{ 'invalid-feedback': hasErrors, 'valid-feedback': !hasErrors }"
v-if="hasErrors">
<template v-if="showSingleError">{{ firstErrorMessage }}</template>
<template v-else>
<div
v-for="errorMessage in activeErrorMessages"
:key="errorMessage"
>
{{ errorMessage }}
</div>
</template>
</div>
</slot>
</div>
</template>

<script>
import singleErrorExtractorMixin from '../../single-error-extractor-mixin'
export default {
name: 'Bootstrap4',
mixins: [singleErrorExtractorMixin],
computed: {
attributes () {
return {
class: { 'form-control': true, 'is-invalid': this.hasErrors, 'is-valid': this.isValid },
name: this.name || undefined
}
}
}
}
</script>
26 changes: 20 additions & 6 deletions src/templates/single-error-extractor/foundation6.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
<template>
<div class="form-group"
:class="{error: hasErrors}">
<div
class="form-group"
:class="{error: hasErrors}">
<slot name="label">
<label
:class="{'is-invalid-label': hasErrors}"
v-if="label">{{ label }} {{ errors ? '*' : '' }}</label>
</slot>
<slot
:attributes="attributes"
:errorMessages="activeErrorMessages"
:errors="activeErrors"
:has-errors="hasErrors"
:events="events"
:first-error-message="firstErrorMessage"
:errorMessages="activeErrorMessages"/>
:has-errors="hasErrors"
:validator="preferredValidator"
/>
<slot
name="errors"
:errors="activeErrors"
:errorMessages="activeErrorMessages"
:has-errors="hasErrors"
:first-error-message="firstErrorMessage">
<div class="form-error is-visible" v-if="hasErrors">
<div
class="form-error is-visible"
v-if="hasErrors">
<span
v-if="showSingleError"
:data-validation-attr="firstError.validationKey">
Expand All @@ -39,6 +46,13 @@
import singleErrorExtractorMixin from '../../single-error-extractor-mixin'
export default {
mixins: [singleErrorExtractorMixin]
mixins: [singleErrorExtractorMixin],
computed: {
attributes () {
return {
class: { 'is-invalid-input': this.hasErrors }
}
}
}
}
</script>
4 changes: 3 additions & 1 deletion src/templates/single-error-extractor/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import foundation6 from './foundation6.vue'
import bootstrap3 from './bootstrap3.vue'
import bootstrap4 from './bootstrap4.vue'

export default {
foundation6,
bootstrap3
bootstrap3,
bootstrap4
}
20 changes: 16 additions & 4 deletions test/dev/testForm.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
<template>
<div>
<form-wrapper :validator="$v.nestedObject">
<form-wrapper :validator="$v.nestedObject" :messages="messages">
<multi-error-extractor/>
<form-group name="first_name" label="First name">
<template slot-scope="{ validator, hasErrors, attributes, events }">
<input
v-bind="attributes"
v-on="events"
type="text"
v-model="nestedObject.first_name">
</template>
</form-group>
<form-group label="Test" attribute="Test Field">
<input type="text"
v-model="test"
Expand Down Expand Up @@ -32,8 +41,8 @@
</div>
</template>
<script>
import { required, minLength, maxLength, email } from 'vuelidate/lib/validators'
import MultiErrorExtractor from '../../src/templates/multi-error-extractor/foundation6'
import { required, minLength, maxLength, email, numeric } from 'vuelidate/lib/validators'
import MultiErrorExtractor from '../../src/templates/multi-error-extractor/bootstrap3'
import formWrapper from '../../src/templates/form-wrapper'
export default {
Expand Down Expand Up @@ -61,6 +70,9 @@ export default {
'address.street': 'Street',
'address.city': 'City',
'address.postal': 'Postal Code'
},
messages: {
numeric: '{attribute} needs to be numeric.'
}
}
},
Expand All @@ -71,7 +83,7 @@ export default {
maxLength: maxLength(10)
},
nestedObject: {
first_name: { required, minLength: minLength(3), maxLength: maxLength(20) },
first_name: { required, minLength: minLength(3), maxLength: maxLength(20), numeric },
last_name: { required, minLength: minLength(3), maxLength: maxLength(20) },
email: { required, email },
address: {
Expand Down

0 comments on commit 39e185d

Please sign in to comment.