Permalink
Browse files

feat(form-group): add valid feedback support (#1360)

* feat(form-feedback): alias form-invalid-feedback

In preparation for the valid-feedback class

* Create form-valid-feedback.js

* Create form-invalid-feedback.js

* Create form-invalid-feedback.html

* Create form-invalid-feedback.js

* Update index.js

* Update package.json

* Rename form-feedback.js to form-valid-feedback.js

* Update and rename form-feedback.html to form-valid-feedback.html

* [form-group] Add valid feedback support

* Delete form-feedback.js

* Update README.md

* [form-group] document valid feedback

* Update and rename form-feedback.spec.js to form-invalid-feedback.spec.js

* Create form-valid-feedback.spec.js

* Update form-valid-feedback.html

* Update form-invalid-feedback.html

* Update form-valid-feedback.html

* Update form-valid-feedback.spec.js

* Update form-invalid-feedback.spec.js

* Update form-group.vue

* Update package.json

* Update form-group.vue

* Update README.md

* Update form-group.vue
  • Loading branch information...
tmorehouse committed Nov 18, 2017
1 parent 3a7517f commit 7f3535b377a11605453ea17ea393ffcc56436901
@@ -10,7 +10,8 @@ as well as contextual state visual feedback.
id="fieldset1"
description="Let us know your name."
label="Enter your name"
:feedback="feedback"
:feedback="feedback"
valid-feedback="Thank you"
:state="state"
>
<b-form-input id="input1" :state="state" v-model.trim="name"></b-form-input>
@@ -74,31 +75,9 @@ Optional descriptive text which is always shown with the `.text-muted` class
(html supported) by setting the `description` prop or using the named slot `description`.
The description text is rendered using the <`b-form-text>` component.
## Invalid feedback
Show optional feedback text to provide textual state feedback (html supported)
by setting the prop `feedback` or using the named slot `feedback`.
## Validation feedback
Note that the feedback **will not be shown** unless the invalid `state` is set on the
`<b-form-group>` and it's child(ren) input(s) or just on the input (`<b-form-input>`,
`<b-form-textarea>`, `<b-form-select>`, `<b-form-checkbox>`, `<b-form-radio>`, or `<b-form-file>`).
Also feedback will be shown if the parent `<b-form>` component does not have the
`novalidate` prop set (or set to `false`) along with `vadidated`
prop set (and the input fails browser native validation constraintes such as `required`).
Refer to Bootstrap V4's `Form` component documentation for details on validation methods.
Invalid feedback is rendered using the `<b-form-feedback>` componment.
**Note:** When using `<b-input-group>`, `<b-form-file>`, `<b-form-radio-group>`,
`<b-form-radio>`, `<b-form-checkbox-group>` or `<b-form-checkbox>` inside a
`<b-form-group>`, setting an invalid `state` on the `input` alone will **not** trigger
the invalid feeback to show (due to limitations with the new Bootsrap V4 validation CSS).
To get around this, **you must also** set the invalid `state` on `<b-form-group>`. Native
browser validation will **not** trigger the invalid feedback to show when using one of
the above mentiond form controls.
## Contextual visual state
### Contextual validation visual state
Bootstrap includes validation styles for `valid` and `invalid` states
on most form controls.
@@ -115,6 +94,47 @@ to `'invalid'` (or `false`), `'valid'` (or `true`), or `null`.
You should always provide content via the `feedback` prop (or slot) to aid users
using assistive technologies when setting a contextual `invalid` state.
### Invalid feedback
Show optional invalid state feedback text to provide textual state feedback (html supported)
by setting the prop `feedback` or using the named slot `invalid-feedback`.
Note that the invalid feedback **will not be shown** unless the invalid `state` is set on the
`<b-form-group>` and it's child(ren) input(s) or just on the input (`<b-form-input>`,
`<b-form-textarea>`, `<b-form-select>`, `<b-form-checkbox>`, `<b-form-radio>`, or `<b-form-file>`).
Also feedback will be shown if the parent `<b-form>` component does not have the
`novalidate` prop set (or set to `false`) along with `vadidated`
prop set (and the input fails browser native validation constraintes such as `required`).
Refer to Bootstrap V4's `Form` component documentation for details on validation methods.
Invalid feedback is rendered using the `<b-form-invalid-feedback>` componment.
### Valid feedback
Show optional valid state feedback text to provide textual state feedback (html supported)
by setting the prop `valid-feedback` or using the named slot `valid-feedback`.
Note that the valid feedback **will not be shown** unless the valid `state` is set on the
`<b-form-group>` and it's child(ren) input(s) or just on the input (`<b-form-input>`,
`<b-form-textarea>`, `<b-form-select>`, `<b-form-checkbox>`, `<b-form-radio>`, or `<b-form-file>`).
Also feedback will be shown if the parent `<b-form>` component does not have the
`novalidate` prop set (or set to `false`) along with `vadidated`
prop set (and the input pases browser native validation constraintes such as `required`).
Refer to Bootstrap V4's `Form` component documentation for details on validation methods.
Valid feedback is rendered using the `<b-form-valid-feedback>` componment.
### Feeback limitations
**Note:** When using `<b-input-group>`, `<b-form-file>`, `<b-form-radio-group>`,
`<b-form-radio>`, `<b-form-checkbox-group>` or `<b-form-checkbox>` inside a
`<b-form-group>`, setting an invalid (or valid) `state` on the `input` alone will **not** trigger
the invalid (or valid) feeback to show (due to limitations with the new Bootsrap V4 validation CSS).
To get around this, **you must also** set the invalid/valid `state` on `<b-form-group>`. Native
browser validation will **not** trigger the invalid feedback to show when using one of
the above mentiond form controls.
## Accessibility
To enable auto-generation of `aria-*` attributes, you should supply a unique `id` prop
to `<b-form-group>`. This will associate the help text and feeback text to
@@ -11,6 +11,12 @@
.b-form-group.form-group.is-valid .invalid-feedback {
display: none !important;
}
.b-form-group.form-group.is-valid .valid-feedback {
display: block !important;
}
.b-form-group.form-group.is-invalid .valid-feedback {
display: none !important;
}
</style>
<script>
@@ -19,11 +25,12 @@
import { idMixin, formStateMixin } from '../../mixins';
import bFormRow from '../form/form-row';
import bFormText from '../form/form-text';
import bFormFeedback from '../form/form-feedback';
import bFormInvalidFeedback from '../form/form-invalid-feedback';
import bFormValidFeedback from '../form/form-valid-feedback';
export default {
mixins: [idMixin, formStateMixin],
components: { bFormRow, bFormText, bFormFeedback },
components: { bFormRow, bFormText, bFormInvalidFeedback, bFormValidFeedback },
render(h) {
const t = this;
const $slots = t.$slots;
@@ -39,37 +46,78 @@
}
// Invalid feeback text
const feedback = h(
'b-form-feedback',
{
directives: [
{ name: 'show', value: t.feedback || $slots.feedback }
],
attrs: {
id: t.feedbackId,
role: 'alert',
'aria-live': 'assertive',
'aria-atomic': 'true'
}
},
[ $slots.feedback || h('span', { domProps: { innerHTML: t.feedback || '' } }) ]
);
let invalidFeedback = h(false);
if (t.feedback || $slots['invalid-feedback'] || $slots['feedback']) {
invalidFeedback = h(
'b-form-invalid-feedback',
{
directives: [
{
name: 'show',
rawName: 'v-show',
value: Boolean(t.feedback || $slots['invalid-feedback'] || $slots['feedback']),
expression: "Boolean(t.feedback || $slots['invalid-feedback'] || $slots['feedback'])",
}
],
attrs: {
id: t.feedbackId,
role: 'alert',
'aria-live': 'assertive',
'aria-atomic': 'true'
}
},
[
t.computedState === false
? ($slots['invalid-feedback'] || $slots['feedback'] || h('span', { domProps: { innerHTML: t.feedback || '' } }))
: h(false)
]
);
}
// Valid feeback text
let validFeedback = h(false);
if (t.validFeedback || $slots['valid-feedback']) {
validFeedback = h(
'b-form-valid-feedback',
{
directives: [
{
name: 'show',
rawName: 'v-show',
value: Boolean(t.validFeedback || $slots['valid-feedback']),
expression: "Boolean(t.validFeedback || $slots['valid-feedback'])"
}
],
attrs: {
id: t.validFeedbackId,
role: 'alert',
'aria-live': 'assertive',
'aria-atomic': 'true'
}
},
[
t.computedState === true
? ($slots['valid-feedback'] || h('span', { domProps: { innerHTML: t.validFeedback || '' } }))
: h(false)
]
);
}
// Form help text (description)
let description = h(false);
if (t.description || $slots.description) {
if (t.description || $slots['description']) {
description = h(
'b-form-text',
{ attrs: { id: t.descriptionId } },
[ $slots.description || h('span', { domProps: { innerHTML: t.description || '' } }) ]
[ $slots['description'] || h('span', { domProps: { innerHTML: t.description || '' } }) ]
);
}
// Build layout
const content = h(
'div',
{ ref: 'content', class: t.inputLayoutClasses },
[ $slots.default, feedback, description ]
[ $slots.default, invalidFeedback, validFeedback, description ]
);
// Generate fieldset wrapper
@@ -122,6 +170,10 @@
type: String,
default: null
},
validFeedback: {
type: String,
default: null
},
validated: {
type: Boolean,
value: false
@@ -173,20 +225,24 @@
return null;
},
feedbackId() {
if (this.feedback || this.$slots['feedback']) {
return this.safeId('_BV_feedback_');
if (this.feedback || this.$slots['invalid-feedback'] || this.$slots['feedback']) {
return this.safeId('_BV_feedback_invalid_');
}
return null;
},
describedByIds() {
if (this.id) {
return [
this.labelId,
this.descriptionId,
this.feedbackId
].filter(i => i).join(' ');
validFeedbackId() {
if (this.validFeedback || this.$slots['valid-feedback']) {
return this.safeId('_BV_feedback_valid_');
}
return null;
},
describedByIds() {
return [
this.labelId,
this.descriptionId,
this.computedState === false ? this.feedbackId : null,
this.computedState === true ? this.validFeedbackId : null
].filter(i => i).join(' ') || null;
}
},
}
@@ -14,8 +14,16 @@
"description": "Content to place in the description area."
},
{
"name": "feedback",
"name": "invalid-feedback",
"description": "Content to place in the invalid feedback area"
},
{
"name": "valid-feedback",
"description": "Content to place in the valid feedback area"
},
{
"name": "feedback",
"description": "Content to place in the invalid feedback area. Deprecated: use 'invalid-feedback' slot instead"
}
]
}
@@ -155,7 +155,8 @@ See also:
- [`<b-form-row>`](/docs/components/layout) create grid rows and columns with tighter margins
- `<b-form-text>` Help text blocks for inputs
- `<b-form-feedback>` Invalid feedback text blocks for input `invalid` states
- `<b-form-invalid-feedback>` Invalid feedback text blocks for input `invalid` states (alias `<b-form-feedback>`)
- `<b-form-valid-feedback>` Valid feedback text blocks for input `valid` states
## Validation

This file was deleted.

Oops, something went wrong.
@@ -0,0 +1,4 @@
<div id="app">
<!-- default -->
<b-form-invalid-feedback ref="default" id="default">default</b-form-invalid-feedback>
</div>
@@ -0,0 +1,4 @@
<div id="app">
<!-- default -->
<b-form-valid-feedback ref="default" id="default">default</b-form-valid-feedback>
</div>
@@ -0,0 +1,3 @@
window.app = new Vue({
el: '#app'
})
@@ -1,7 +1,7 @@
import { loadFixture, testVM } from '../../../tests/utils'
describe('form-feedback', async () => {
beforeEach(loadFixture(__dirname, 'form-feedback'))
describe('form-invalid-feedback', async () => {
beforeEach(loadFixture(__dirname, 'form-invalid-feedback'))
testVM()
it('default should have tag div', async () => {
@@ -0,0 +1,29 @@
import { mergeData } from '../../utils'
export const props = {
id: {
type: String,
default: null
},
tag: {
type: String,
default: 'div'
}
}
export default {
functional: true,
props,
render (h, { props, data, children }) {
return h(
props.tag,
mergeData(data, {
staticClass: 'valid-feedback',
attrs: {
id: props.id
}
}),
children
)
}
}
@@ -0,0 +1,16 @@
import { loadFixture, testVM } from '../../../tests/utils'
describe('form-valid-feedback', async () => {
beforeEach(loadFixture(__dirname, 'form-valid-feedback'))
testVM()
it('default should have tag div', async () => {
const { app: { $refs } } = window
expect($refs.default).toBeElement('div')
})
it('default should contain base class', async () => {
const { app: { $refs } } = window
expect($refs.default).toHaveClass('valid-feedback')
})
})
@@ -1,7 +1,8 @@
import bForm from './form'
import bFormRow from './form-row'
import bFormText from './form-text'
import bFormFeedback from './form-feedback'
import bFormInvalidFeedback from './form-invalid-feedback'
import bFormValidFeedback from './form-valid-feedback'
import { registerComponents, vueUse } from '../../utils'
/* eslint-disable no-var, no-undef, guard-for-in, object-shorthand */
@@ -10,7 +11,9 @@ const components = {
bForm,
bFormRow,
bFormText,
bFormFeedback
bFormInvalidFeedback,
bFormFeedback: bFormInvalidFeedback,
bFormValidFeedback
}
const VuePlugin = {
@@ -7,7 +7,8 @@
"component": "bForm",
"components": [
"bFormText",
"bFormFeedback",
"bFormInvalidFeedback",
"bFormValidFeedback",
"bFormRow"
],
"events": [

0 comments on commit 7f3535b

Please sign in to comment.