Skip to content
Permalink
Browse files

feat(b-form-input, b-form-textarea): add `v-model` debouncing feature…

…, and deprecate `<b-table>` prop `filter-debounce` (closes #4150) (#4314)
  • Loading branch information
tmorehouse committed Oct 30, 2019
1 parent 8740210 commit 3ecdfa2a4cd24f5001c30cf7281964451848e87d
@@ -229,7 +229,8 @@ Guide for full details on setting up aliases for [webpack](https://webpack.js.or
## Tree shaking with module bundlers

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

<div class="alert alert-info">
<p class="mb-0">
@@ -327,8 +327,8 @@ attribute on the input will automatically be set to `'true'`;

## Formatter support

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

Formatting (when a formatter function is supplied) occurs when the control's native `input` and
`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.
```html
<template>
<div>
<label for="input-formatter">Text input with formatter (on input)</label>
<b-form-input
id="input-formatter"
v-model="text1"
:formatter="format"
placeholder="Enter your name"
aria-describedby="input-formatter-help"
></b-form-input>
<b-form-text id="input-formatter-help">
We will convert your name to lowercase instantly
</b-form-text>
<div>Value: {{ text1 }}</div>

<label for="input-lazy">Text input with lazy formatter (on blur)</label>
<b-form-input
id="input-lazy"
v-model="text2"
:formatter="format"
placeholder="Enter your name"
aria-describedby="input-lazy-help"
lazy-formatter
></b-form-input>
<b-form-text id="input-lazy-help">This one is a little lazy!</b-form-text>
<div>Value: {{ text2 }}</div>
<b-form-group
class="mb-0"
label="Text input with formatter (on input)"
label-for="input-formatter"
description="We will convert your name to lowercase instantly"
>
<b-form-input
id="input-formatter"
v-model="text1"
placeholder="Enter your name"
:formatter="format"
></b-form-input>
</b-form-group>
<p><b>Value:</b> {{ text1 }}</p>

<b-form-group
class="mb-0"
label="Text input with lazy formatter (on blur)"
label-for="input-lazy"
description="This one is a little lazy!"
>
<b-form-input
id="input-lazy"
v-model="text2"
placeholder="Enter your name"
lazy-formatter
:formatter="format"
></b-form-input>
</b-form-group>
<p class="mb-0"><b>Value:</b> {{ text2 }}</p>
</div>
</template>

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

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

**Notes:**

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

## Debounce support

As an alternative to the `lazy` modifier prop, `<b-form-input>` optionally supports debouncing user
input, updating the `v-model` after a period of idle time from when the last character was entered
by the user (or a `change` event occurs). If the user enters a new character (or deletes characters)
before the idle timeout expires, the timeout is re-started.

To enable debouncing, set the prop `debounce` to any integer greater than zero. The value is
specified in milliseconds. Setting `debounce` to `0` will disable debouncing.

Note: debouncing will _not_ occur if the `lazy` prop is set.

```html
<template>
<div>
<b-form-input v-model="value" type="text" debounce="500"></b-form-input>
<div class="mt-2">Value: "{{ value }}"</div>
</div>
</template>

<script>
export default {
data() {
return {
value: ''
}
}
}
</script>

<!-- b-form-input-debounce.vue -->
```

## Autofocus

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" ...>`
Refer to https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement for more information on
these methods and properties. Support will vary based on input type.

## Component alias

You can use `<b-form-input>` by it's shorter alias `<b-input>`.

## Using HTML5 `<input>` as an alternative

If you just need a simple input with basic bootstrap styling, you can simply use the following:
@@ -766,6 +766,84 @@ describe('form-input', () => {
wrapper.destroy()
})

it('"debounce" prop works', async () => {
jest.useFakeTimers()
const wrapper = mount(BFormInput, {
propsData: {
type: 'text',
value: '',
debounce: 100
}
})

const input = wrapper.find('input')
input.element.value = 'a'
input.trigger('input')
await waitNT(wrapper.vm)
expect(input.element.value).toBe('a')
// `v-model` update event should not have emitted
expect(wrapper.emitted('update')).not.toBeDefined()
// `input` event should be emitted
expect(wrapper.emitted('input')).toBeDefined()
expect(wrapper.emitted('input').length).toBe(1)
expect(wrapper.emitted('input')[0][0]).toBe('a')

input.element.value = 'ab'
input.trigger('input')
await waitNT(wrapper.vm)
expect(input.element.value).toBe('ab')
// `v-model` update event should not have emitted
expect(wrapper.emitted('update')).not.toBeDefined()
// `input` event should be emitted
expect(wrapper.emitted('input').length).toBe(2)
expect(wrapper.emitted('input')[1][0]).toBe('ab')

// Advance timer
jest.runOnlyPendingTimers()
// Should update the v-model
expect(input.element.value).toBe('ab')
// `v-model` update event should have emitted
expect(wrapper.emitted('update')).toBeDefined()
expect(wrapper.emitted('update').length).toBe(1)
expect(wrapper.emitted('update')[0][0]).toBe('ab')
// `input` event should not have emitted new event
expect(wrapper.emitted('input').length).toBe(2)

// Update input
input.element.value = 'abc'
input.trigger('input')
await waitNT(wrapper.vm)
expect(input.element.value).toBe('abc')
// `v-model` update event should not have emitted new event
expect(wrapper.emitted('update').length).toBe(1)
// `input` event should be emitted
expect(wrapper.emitted('input').length).toBe(3)
expect(wrapper.emitted('input')[2][0]).toBe('abc')

// Update input
input.element.value = 'abcd'
input.trigger('input')
await waitNT(wrapper.vm)
expect(input.element.value).toBe('abcd')
// `v-model` update event should not have emitted new event
expect(wrapper.emitted('update').length).toEqual(1)
// `input` event should be emitted
expect(wrapper.emitted('input').length).toBe(4)
expect(wrapper.emitted('input')[3][0]).toBe('abcd')

// Trigger a `change` event
input.trigger('change')
await waitNT(wrapper.vm)
expect(input.element.value).toBe('abcd')
// `v-model` update event should have emitted (change overrides debounce)
expect(wrapper.emitted('update').length).toEqual(2)
expect(wrapper.emitted('update')[1][0]).toBe('abcd')
// `input` event should not have emitted new event
expect(wrapper.emitted('input').length).toBe(4)

wrapper.destroy()
})

it('focus() and blur() methods work', async () => {
const wrapper = mount(BFormInput, {
mountToDocument: true
@@ -790,7 +868,6 @@ describe('form-input', () => {

beforeEach(() => {
// Mock getBCR so that the isVisible(el) test returns true
// In our test below, all pagination buttons would normally be visible
Element.prototype.getBoundingClientRect = jest.fn(() => ({
width: 24,
height: 24,
@@ -32,6 +32,11 @@
"version": "2.1.0",
"description": "When set, updates the v-model on 'change'/'blur' events instead of 'input'. Emulates the Vue '.lazy' v-model modifier"
},
{
"prop": "debounce",
"version": "2.1.0",
"description": "When set to a number of milliseconds greater than zero, will debounce the user input. Has no effect if prop 'lazy' is set"
},
{
"prop": "type",
"description": "The type of input to render. See the docs for supported types"

0 comments on commit 3ecdfa2

Please sign in to comment.
You can’t perform that action at this time.