Skip to content

Commit 3ecdfa2

Browse files
authored
feat(b-form-input, b-form-textarea): add v-model debouncing feature, and deprecate <b-table> prop filter-debounce (closes #4150) (#4314)
1 parent 8740210 commit 3ecdfa2

File tree

11 files changed

+407
-130
lines changed

11 files changed

+407
-130
lines changed

docs/markdown/intro/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ Guide for full details on setting up aliases for [webpack](https://webpack.js.or
229229
## Tree shaking with module bundlers
230230

231231
When using a module bundler you can optionally import only specific components groups (plugins),
232-
components and/or directives. Note tree shaking only applies to the JavaScript code and not CSS/SCSS.
232+
components and/or directives. Note tree shaking only applies to the JavaScript code and not
233+
CSS/SCSS.
233234

234235
<div class="alert alert-info">
235236
<p class="mb-0">

src/components/form-input/README.md

Lines changed: 68 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,8 @@ attribute on the input will automatically be set to `'true'`;
327327

328328
## Formatter support
329329

330-
`<b-form-input>` and `<b-form-textarea>` optionally supports formatting by passing a function
331-
reference to the `formatter` prop.
330+
`<b-form-input>` optionally supports formatting by passing a function reference to the `formatter`
331+
prop.
332332

333333
Formatting (when a formatter function is supplied) occurs when the control's native `input` and
334334
`change` events fire. You can use the boolean prop `lazy-formatter` to restrict the formatter
@@ -344,30 +344,36 @@ Formatting does not occur if a `formatter` is not provided.
344344
```html
345345
<template>
346346
<div>
347-
<label for="input-formatter">Text input with formatter (on input)</label>
348-
<b-form-input
349-
id="input-formatter"
350-
v-model="text1"
351-
:formatter="format"
352-
placeholder="Enter your name"
353-
aria-describedby="input-formatter-help"
354-
></b-form-input>
355-
<b-form-text id="input-formatter-help">
356-
We will convert your name to lowercase instantly
357-
</b-form-text>
358-
<div>Value: {{ text1 }}</div>
359-
360-
<label for="input-lazy">Text input with lazy formatter (on blur)</label>
361-
<b-form-input
362-
id="input-lazy"
363-
v-model="text2"
364-
:formatter="format"
365-
placeholder="Enter your name"
366-
aria-describedby="input-lazy-help"
367-
lazy-formatter
368-
></b-form-input>
369-
<b-form-text id="input-lazy-help">This one is a little lazy!</b-form-text>
370-
<div>Value: {{ text2 }}</div>
347+
<b-form-group
348+
class="mb-0"
349+
label="Text input with formatter (on input)"
350+
label-for="input-formatter"
351+
description="We will convert your name to lowercase instantly"
352+
>
353+
<b-form-input
354+
id="input-formatter"
355+
v-model="text1"
356+
placeholder="Enter your name"
357+
:formatter="format"
358+
></b-form-input>
359+
</b-form-group>
360+
<p><b>Value:</b> {{ text1 }}</p>
361+
362+
<b-form-group
363+
class="mb-0"
364+
label="Text input with lazy formatter (on blur)"
365+
label-for="input-lazy"
366+
description="This one is a little lazy!"
367+
>
368+
<b-form-input
369+
id="input-lazy"
370+
v-model="text2"
371+
placeholder="Enter your name"
372+
lazy-formatter
373+
:formatter="format"
374+
></b-form-input>
375+
</b-form-group>
376+
<p class="mb-0"><b>Value:</b> {{ text2 }}</p>
371377
</div>
372378
</template>
373379

@@ -464,9 +470,9 @@ from an array of options.
464470
Vue does not officially support `.lazy`, `.trim`, and `.number` modifiers on the `v-model` of custom
465471
component based inputs, and may generate a bad user experience. Avoid using Vue's native modifiers.
466472

467-
To get around this, `<b-form-input>` and `<b-form-textarea>` have three boolean props `trim`,
468-
`number`, and `lazy` which emulate the native Vue `v-model` modifiers `.trim` and `.number` and
469-
`.lazy` respectively. The `lazy` prop will update the v-model on `change`/`blur`events.
473+
To get around this, `<b-form-input>` has three boolean props `trim`, `number`, and `lazy` which
474+
emulate the native Vue `v-model` modifiers `.trim` and `.number` and `.lazy` respectively. The
475+
`lazy` prop will update the v-model on `change`/`blur`events.
470476

471477
**Notes:**
472478

@@ -480,6 +486,39 @@ To get around this, `<b-form-input>` and `<b-form-textarea>` have three boolean
480486
optional formatting (which may not match the value returned via the `v-model` `update` event,
481487
which handles the modifiers).
482488

489+
## Debounce support
490+
491+
As an alternative to the `lazy` modifier prop, `<b-form-input>` optionally supports debouncing user
492+
input, updating the `v-model` after a period of idle time from when the last character was entered
493+
by the user (or a `change` event occurs). If the user enters a new character (or deletes characters)
494+
before the idle timeout expires, the timeout is re-started.
495+
496+
To enable debouncing, set the prop `debounce` to any integer greater than zero. The value is
497+
specified in milliseconds. Setting `debounce` to `0` will disable debouncing.
498+
499+
Note: debouncing will _not_ occur if the `lazy` prop is set.
500+
501+
```html
502+
<template>
503+
<div>
504+
<b-form-input v-model="value" type="text" debounce="500"></b-form-input>
505+
<div class="mt-2">Value: "{{ value }}"</div>
506+
</div>
507+
</template>
508+
509+
<script>
510+
export default {
511+
data() {
512+
return {
513+
value: ''
514+
}
515+
}
516+
}
517+
</script>
518+
519+
<!-- b-form-input-debounce.vue -->
520+
```
521+
483522
## Autofocus
484523

485524
When the `autofocus` prop is set, the input will be auto-focused when it is inserted (i.e.
@@ -533,10 +572,6 @@ component reference (i.e. assign a `ref` to your `<b-form-input ref="foo" ...>`
533572
Refer to https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement for more information on
534573
these methods and properties. Support will vary based on input type.
535574

536-
## Component alias
537-
538-
You can use `<b-form-input>` by it's shorter alias `<b-input>`.
539-
540575
## Using HTML5 `<input>` as an alternative
541576

542577
If you just need a simple input with basic bootstrap styling, you can simply use the following:

src/components/form-input/form-input.spec.js

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,84 @@ describe('form-input', () => {
766766
wrapper.destroy()
767767
})
768768

769+
it('"debounce" prop works', async () => {
770+
jest.useFakeTimers()
771+
const wrapper = mount(BFormInput, {
772+
propsData: {
773+
type: 'text',
774+
value: '',
775+
debounce: 100
776+
}
777+
})
778+
779+
const input = wrapper.find('input')
780+
input.element.value = 'a'
781+
input.trigger('input')
782+
await waitNT(wrapper.vm)
783+
expect(input.element.value).toBe('a')
784+
// `v-model` update event should not have emitted
785+
expect(wrapper.emitted('update')).not.toBeDefined()
786+
// `input` event should be emitted
787+
expect(wrapper.emitted('input')).toBeDefined()
788+
expect(wrapper.emitted('input').length).toBe(1)
789+
expect(wrapper.emitted('input')[0][0]).toBe('a')
790+
791+
input.element.value = 'ab'
792+
input.trigger('input')
793+
await waitNT(wrapper.vm)
794+
expect(input.element.value).toBe('ab')
795+
// `v-model` update event should not have emitted
796+
expect(wrapper.emitted('update')).not.toBeDefined()
797+
// `input` event should be emitted
798+
expect(wrapper.emitted('input').length).toBe(2)
799+
expect(wrapper.emitted('input')[1][0]).toBe('ab')
800+
801+
// Advance timer
802+
jest.runOnlyPendingTimers()
803+
// Should update the v-model
804+
expect(input.element.value).toBe('ab')
805+
// `v-model` update event should have emitted
806+
expect(wrapper.emitted('update')).toBeDefined()
807+
expect(wrapper.emitted('update').length).toBe(1)
808+
expect(wrapper.emitted('update')[0][0]).toBe('ab')
809+
// `input` event should not have emitted new event
810+
expect(wrapper.emitted('input').length).toBe(2)
811+
812+
// Update input
813+
input.element.value = 'abc'
814+
input.trigger('input')
815+
await waitNT(wrapper.vm)
816+
expect(input.element.value).toBe('abc')
817+
// `v-model` update event should not have emitted new event
818+
expect(wrapper.emitted('update').length).toBe(1)
819+
// `input` event should be emitted
820+
expect(wrapper.emitted('input').length).toBe(3)
821+
expect(wrapper.emitted('input')[2][0]).toBe('abc')
822+
823+
// Update input
824+
input.element.value = 'abcd'
825+
input.trigger('input')
826+
await waitNT(wrapper.vm)
827+
expect(input.element.value).toBe('abcd')
828+
// `v-model` update event should not have emitted new event
829+
expect(wrapper.emitted('update').length).toEqual(1)
830+
// `input` event should be emitted
831+
expect(wrapper.emitted('input').length).toBe(4)
832+
expect(wrapper.emitted('input')[3][0]).toBe('abcd')
833+
834+
// Trigger a `change` event
835+
input.trigger('change')
836+
await waitNT(wrapper.vm)
837+
expect(input.element.value).toBe('abcd')
838+
// `v-model` update event should have emitted (change overrides debounce)
839+
expect(wrapper.emitted('update').length).toEqual(2)
840+
expect(wrapper.emitted('update')[1][0]).toBe('abcd')
841+
// `input` event should not have emitted new event
842+
expect(wrapper.emitted('input').length).toBe(4)
843+
844+
wrapper.destroy()
845+
})
846+
769847
it('focus() and blur() methods work', async () => {
770848
const wrapper = mount(BFormInput, {
771849
mountToDocument: true
@@ -790,7 +868,6 @@ describe('form-input', () => {
790868

791869
beforeEach(() => {
792870
// Mock getBCR so that the isVisible(el) test returns true
793-
// In our test below, all pagination buttons would normally be visible
794871
Element.prototype.getBoundingClientRect = jest.fn(() => ({
795872
width: 24,
796873
height: 24,

src/components/form-input/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
"version": "2.1.0",
3333
"description": "When set, updates the v-model on 'change'/'blur' events instead of 'input'. Emulates the Vue '.lazy' v-model modifier"
3434
},
35+
{
36+
"prop": "debounce",
37+
"version": "2.1.0",
38+
"description": "When set to a number of milliseconds greater than zero, will debounce the user input. Has no effect if prop 'lazy' is set"
39+
},
3540
{
3641
"prop": "type",
3742
"description": "The type of input to render. See the docs for supported types"

0 commit comments

Comments
 (0)