Skip to content

Commit 7e1c641

Browse files
mosinvetmorehouse
authored andcommitted
feat(table) busy state self-handling (bootstrap-vue#881)
* WIP feat(table) busy state self-handling * WIP feat(table) busy state self-handling * some fixes * [table] doc tweaks :) * docs update * Update README.md * docs minor typo fix
1 parent 2fade17 commit 7e1c641

File tree

2 files changed

+79
-53
lines changed

2 files changed

+79
-53
lines changed

docs/components/table/README.md

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ custom rendering, events, and asynchronous data.
1818
</b-form-fieldset>
1919
</div>
2020
</div>
21-
2221
<div class="row my-1">
2322
<div class="col-sm-8">
2423
<b-pagination :total-rows="totalRows" :per-page="perPage" v-model="currentPage" />
@@ -150,15 +149,15 @@ Supported field properties:
150149
| `thStyle` | Object | JavaScript object representing CSS styles you would like to apply to the table field `<th>`
151150
| `formatter` | String or Function | A formatter callback function, can be used instead of slots for real table fields (i.e. fields, that have corresponding data at items array)
152151

153-
Notes:
154-
- Field properties, if not present, default to null unless otherwise stated above.
155-
- `thClass` and `tdClass` will not work with classes that are defined in scoped CSS
156-
- For information on the syntax supported by `thStyle`, see
152+
>***Notes:***
153+
>- *Field properties, if not present, default to null unless otherwise stated above.*
154+
>- *`thClass` and `tdClass` will not work with classes that are defined in scoped CSS*
155+
>- *For information on the syntax supported by `thStyle`, see
157156
[Class and Style Bindings](https://vuejs.org/v2/guide/class-and-style.html#Binding-Inline-Styles)
158-
in the Vue.js guide.
159-
- Any additional properties added to the field objects will be left intact - so you can access
160-
them via the named scoped slots for custom data, header, and footer rendering.
161-
- by design, slots and formatter are **mutually exclusive**. Ie. if formatter defined at field props, then slot will not be used even if it defined for this field.
157+
in the Vue.js guide.*
158+
>- *Any additional properties added to the field objects will be left intact - so you can access
159+
them via the named scoped slots for custom data, header, and footer rendering.*
160+
>- *by design, slots and formatter are **mutually exclusive**. Ie. if formatter defined at field props, then slot will not be used even if it defined for this field.*
162161
163162
### Items (record data)
164163
`items` are real table data record objects in array format. Example format:
@@ -193,8 +192,8 @@ Supported optional item record modifier properties (make sure your field keys do
193192
| `_cellVariants` | Object | Bootstrap contextual state applied to individual cells. Keyed by field (Supported values: `active`, `success`, `info`, `warning`, `danger`)
194193
| `state` | String | **deprecated** in favour of `_rowVariant`
195194

196-
**Note** `state` is deprecated. The property `_rowVariant`, if present in
197-
the record, will be prefered.
195+
>***Note:** `state` is deprecated. The property `_rowVariant`, if present in
196+
the record, will be prefered.*
198197

199198
`items` can also be a reference to a *provider* function, which returns an `Array` of items data.
200199
Provider functions can also be asynchronous:
@@ -229,7 +228,7 @@ See the **"Using Items Provider functions"** section below for more details.
229228
Custom rendering for each data field in a row is possible using either
230229
[scoped slots](http://vuejs.org/v2/guide/components.html#Scoped-Slots) or formatter callback function.
231230

232-
####Slots
231+
#### Slots
233232
If you want to add an extra field which does not exist in the records,
234233
just add it to the `fields` array. Example:
235234

@@ -299,10 +298,10 @@ The slot's scope variable (`data` in the above sample) will have the following p
299298
| `item` | Object | The entire record (i.e. `items[index]`) for this row
300299
| `index` | Number | The row number (zero based)
301300

302-
**Note** that `index` will not always be the actual row's index number, as it is
301+
>***Note:** `index` will not always be the actual row's index number, as it is
303302
computed after pagination and filtering have been applied to the original
304303
table data. The `index` value will refer to the **displayed row number**. This
305-
number will align with the indexes from the optional `v-model` bound variable.
304+
number will align with the indexes from the optional `v-model` bound variable.*
306305

307306
When placing inputs, buttons, selects or links within a data cell scoped slot,
308307
be sure to add a `@click.stop` handler (which can be empty) to prevent the
@@ -398,7 +397,7 @@ the original `items` array (except when `items` is set to a provider function).
398397
Deleteing a record from the v-model will **not** remove the record from the
399398
original items array.
400399

401-
**Note:** Do not bind any value directly to the `value` prop. Use the `v-model` binding.
400+
>***Note:** Do not bind any value directly to the `value` prop. Use the `v-model` binding.*
402401
403402
### Filtering
404403
Filtering, when used, is aplied to the original items array data, and hence it is not
@@ -464,8 +463,8 @@ if (typeof a[key] === 'number' && typeof b[key] === 'number') {
464463
As mentioned under the `items` prop section, it is possible to use a function to provide
465464
the row data (items), by specifying a function reference via the `items` prop.
466465

467-
**Note:** The `items-provider` prop has been deprecated in favour of providing a function
468-
reference to the `items` prop. A console warning will be issued if `items-provider` is used.
466+
>***Note:** The `items-provider` prop has been deprecated in favour of providing a function
467+
reference to the `items` prop. A console warning will be issued if `items-provider` is used.*
469468

470469
The provider function is called with the following signature:
471470

@@ -508,7 +507,7 @@ function myProvider(ctx, callback) {
508507
let items = data.items;
509508
// Provide the array of items to the callabck
510509
callback(items);
511-
})
510+
}).catch(error => {callback([])})
512511

513512
// Must return null or undefined
514513
return null;
@@ -525,17 +524,23 @@ function myProvider(ctx) {
525524
// Pluck the array of items off our axios response
526525
let items = data.items;
527526
// Must return an array of items
528-
return(items);
527+
return(items || []);
529528
});
530529
}
531530
```
532531

533-
`<b-table>` provides a `busy` prop that will flag the table as busy, which you can
534-
set to `true` just before your async fetch, and then set it to `false` once you have
535-
your data, and just before you send it to the table for display. Example:
532+
`<b-table>` automatically tracks/controls it's `busy` state, however it provides
533+
a `busy` prop that can be used either to override inner `busy`state, or to monitor
534+
b-table's current busy state in your application using the 2-way `.sync` modifier.
535+
536+
>***Note:** in order to allow `<b-table>` fully track it's `busy` state, custom items
537+
provider function should handle errors from data sources and return an empty
538+
array to `<b-table>`*
539+
540+
Example:
536541

537542
```html
538-
<b-table id="my-table" :busy="isBusy" :items="myProvider" :fields="fields" ....>
543+
<b-table id="my-table" :busy.sync="isBusy" :items="myProvider" :fields="fields" ....>
539544
</b-table>
540545
```
541546
```js
@@ -546,18 +551,27 @@ data () {
546551
}
547552
methods: {
548553
myProvider(ctx) {
549-
this.isBusy = true
554+
// Here we don't set isBusy prop, so state will be handled by table itself
550555
let promise = axios.get('/some/url');
551556

552557
return promise.then((data) => {
553558
const items = data.items;
559+
// Here we override the state, setting isBusy to false
554560
this.isBusy = false
555561
return(items);
562+
}).catch(error => {
563+
// Returning an empty array, allows table to correctly handle state in case of error
564+
return []
556565
});
557566
}
558567
}
559568
```
560569

570+
>***Note:** If you manually place the table in the `busy` state, the items provider will
571+
__not__ be called/refreshed until the `busy` state has been set to `false`. All click
572+
related events and sort-changed events will __not__ be emiited when in the `busy` state.*
573+
574+
#### Provider Sorting, Paging, Filtering
561575
By default, the items provider function is responsible for **all paging, filtering, and sorting**
562576
of the data, before passing it to `b-table` for display.
563577

lib/components/table.vue

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<table :id="id || null"
3-
:aria-busy="busy ? 'true' : 'false'"
3+
:aria-busy="computedBusy ? 'true' : 'false'"
44
:class="tableClass"
55
>
66
<thead :class="headClass">
@@ -126,13 +126,14 @@
126126
localSortDesc: this.sortDesc || false,
127127
localItems: [],
128128
// Note: filteredItems only used to determine if # of items changed
129-
filteredItems: []
129+
filteredItems: [],
130+
localBusy: this.busy
130131
};
131132
},
132133
props: {
133134
id: {
134135
type: String,
135-
default: ''
136+
'default': ''
136137
},
137138
items: {
138139
type: [Array, Function],
@@ -147,84 +148,84 @@
147148
},
148149
sortBy: {
149150
type: String,
150-
default: null
151+
'default': null
151152
},
152153
sortDesc: {
153154
type: Boolean,
154-
default: false
155+
'default': false
155156
},
156157
apiUrl: {
157158
type: String,
158-
default: ''
159+
'default': ''
159160
},
160161
fields: {
161162
type: Object,
162-
default: {}
163+
'default': {}
163164
},
164165
striped: {
165166
type: Boolean,
166-
default: false
167+
'default': false
167168
},
168169
bordered: {
169170
type: Boolean,
170-
default: false
171+
'default': false
171172
},
172173
inverse: {
173174
type: Boolean,
174-
default: false
175+
'default': false
175176
},
176177
hover: {
177178
type: Boolean,
178-
default: false
179+
'default': false
179180
},
180181
small: {
181182
type: Boolean,
182-
default: false
183+
'default': false
183184
},
184185
responsive: {
185186
type: Boolean,
186-
default: false
187+
'default': false
187188
},
188189
headVariant: {
189190
type: String,
190-
default: ''
191+
'default': ''
191192
},
192193
footVariant: {
193194
type: String,
194-
default: ''
195+
'default': ''
195196
},
196197
perPage: {
197198
type: Number,
198-
default: null
199+
'default': null
199200
},
200201
currentPage: {
201202
type: Number,
202-
default: 1
203+
'default': 1
203204
},
204205
filter: {
205206
type: [String, RegExp, Function],
206-
default: null
207+
'default': null
207208
},
208209
sortCompare: {
209210
type: Function,
210-
default: null
211+
'default': null
211212
},
212213
itemsProvider: {
213214
// Deprecated in favour of items
214215
type: Function,
215-
default: null
216+
'default': null
216217
},
217218
noProviderPaging: {
218219
type: Boolean,
219-
default: false
220+
'default': false
220221
},
221222
noProviderSorting: {
222223
type: Boolean,
223-
default: false
224+
'default': false
224225
},
225226
noProviderFiltering: {
226227
type: Boolean,
227-
default: false
228+
'default': false
228229
},
229230
busy: {
230231
type: Boolean,
@@ -314,11 +315,18 @@
314315
if (oldVal !== newVal && !this.noProviderFiltering) {
315316
this._providerUpdate();
316317
}
318+
},
319+
localBusy(newVal, oldVal) {
320+
if (newVal !== oldVal) {
321+
this.$emit('update:busy', newVal);
322+
}
323+
317324
}
318325
},
319326
mounted() {
320327
this.localSortBy = this.sortBy;
321328
this.localSortDesc = this.sortDesc;
329+
this.localBusy = this.busy;
322330
if (this.hasProvider) {
323331
this._providerUpdate();
324332
}
@@ -428,8 +436,10 @@
428436
429437
// Update the value model with the filtered/sorted/paginated data set
430438
this.$emit('input', items);
431-
432439
return items;
440+
},
441+
computedBusy() {
442+
return this.busy || this.localBusy;
433443
}
434444
},
435445
methods: {
@@ -463,7 +473,7 @@
463473
];
464474
},
465475
rowClicked(e, item, index) {
466-
if (this.busy) {
476+
if (this.computedBusy) {
467477
// If table is busy (via provider) then don't propagate
468478
e.preventDefault();
469479
e.stopPropagation();
@@ -472,7 +482,7 @@
472482
this.$emit('row-clicked', item, index);
473483
},
474484
rowDblClicked(e, item, index) {
475-
if (this.busy) {
485+
if (this.computedBusy) {
476486
// If table is busy (via provider) then don't propagate
477487
e.preventDefault();
478488
e.stopPropagation();
@@ -481,7 +491,7 @@
481491
this.$emit('row-dblclicked', item, index);
482492
},
483493
rowHovered(e, item, index) {
484-
if (this.busy) {
494+
if (this.computedBusy) {
485495
// If table is busy (via provider) then don't propagate
486496
e.preventDefault();
487497
e.stopPropagation();
@@ -490,7 +500,7 @@
490500
this.$emit('row-hovered', item, index);
491501
},
492502
headClicked(e, field, key) {
493-
if (this.busy) {
503+
if (this.computedBusy) {
494504
// If table is busy (via provider) then don't propagate
495505
e.preventDefault();
496506
e.stopPropagation();
@@ -527,16 +537,18 @@
527537
},
528538
_providerSetLocal(items) {
529539
this.localItems = (items && items.length > 0) ? items.slice() : [];
540+
this.localBusy = false;
530541
this.$emit('refreshed');
531542
this.emitOnRoot('table::refreshed', this.id);
532543
},
533544
_providerUpdate() {
534545
// Refresh the provider items
535-
if (this.busy || !this.hasProvider) {
546+
if (this.computedBusy || !this.hasProvider) {
536547
// Don't refresh remote data if we are 'busy' or if no provider
537548
return;
538549
}
539550
551+
this.localBusy = true;
540552
// Call provider function with context and optional callback
541553
const data = this.items(this.context, this._providerSetLocal);
542554

0 commit comments

Comments
 (0)