Skip to content

Commit a654a61

Browse files
authored
feat(table): Add table-busy slot for loading status. Closes #1859 (#2196)
* feat(table): Add table-busy slot for loading status. Fixes #1859 Adds a new slot `table-busy` which allows users to display a loading message when the table is in the busy state. If no `table-busy` slot is present, the current rows are displayed (as was the behaviour before) Closes #1859 * document new slot in package.json * Update README.md * Create table-busy.spec.js Add test suite for b-table busy state
1 parent e0cdca0 commit a654a61

File tree

4 files changed

+325
-111
lines changed

4 files changed

+325
-111
lines changed

src/components/table/README.md

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,73 @@ elements are limited. Refer to [MDN](https://developer.mozilla.org/en-US/docs/We
609609
for details and usage of `<colgroup>`
610610

611611

612+
## Table busy state
613+
`<b-table>` provides a `busy` prop that will flag the table as busy, which you can
614+
set to `true` just before you update your items, and then set it to `false` once you have
615+
your items. When in hte busy state, the tabe will have the attribute `aria-busy="true"`.
616+
617+
During the busy state, the table will be rendered in a "muted" look (`opacity: 0.6`), using the
618+
following custom CSS:
619+
620+
```css
621+
/* Busy table styling */
622+
table.b-table[aria-busy='false'] {
623+
opacity: 1;
624+
}
625+
table.b-table[aria-busy='true'] {
626+
opacity: 0.6;
627+
}
628+
```
629+
630+
You can override this styling using your own CSS.
631+
632+
You may optionally provide a `table-busy` slot to show a custom loading message or spinner
633+
whenever the table's busy state is `true`. The slot will be placed in a `<tr>` element with
634+
class `b-table-busy-slot`, which has one single `<td>` with a `colspan` set to the number of fields.
635+
636+
**Example of `table-busy` slot usage:**
637+
```html
638+
<template>
639+
<div>
640+
<b-button @click="toggleBusy">Toggle Busy State</b-button>
641+
<b-table :items="items" :busy="isBusy" class="mt-3" outlined>
642+
<div slot="table-busy" class="text-center text-danger">
643+
<br><strong>Loading...</strong><br><br>
644+
</div>
645+
</b-table>
646+
</div>
647+
</template>
648+
<script>
649+
export default {
650+
data() {
651+
return {
652+
isBusy: false,
653+
items: [
654+
{ first_name: 'Dickerson', last_name: 'MacDonald', age: 40 },
655+
{ first_name: 'Larsen', last_name: 'Shaw', age: 21 },
656+
{ first_name: 'Geneva', last_name: 'Wilson', age: 89 },
657+
{ first_name: 'Jami', last_name: 'Carney',age: 38 }
658+
]
659+
}
660+
},
661+
methods: {
662+
toggleBusy() {
663+
this.isBusy = !this.isBusy
664+
}
665+
}
666+
}
667+
</script>
668+
669+
<!-- table-busy-slot.vue -->
670+
```
671+
672+
Also see the [**Using Items Provider Functions**](#using-items-provider-functions) below for additional
673+
informaton on the `busy` state.
674+
675+
**Note:** All click related and hover events, and sort-changed events will __not__ be
676+
emitted when the table is in the `busy` state.
677+
678+
612679
## Custom Data Rendering
613680
Custom rendering for each data field in a row is possible using either
614681
[scoped slots](http://vuejs.org/v2/guide/components.html#Scoped-Slots)
@@ -1226,22 +1293,23 @@ function myProvider (ctx) {
12261293
}
12271294
```
12281295

1229-
`<b-table>` automatically tracks/controls it's `busy` state, however it provides
1230-
a `busy` prop that can be used either to override inner `busy`state, or to monitor
1231-
`<b-table>`'s current busy state in your application using the 2-way `.sync` modifier.
1296+
### Automated table busy state
1297+
`<b-table>` automatically tracks/controls it's `busy` state when items provider functions are
1298+
used, however it also provides a `busy` prop that can be used either to override the inner `busy`
1299+
state, or to monitor `<b-table>`'s current busy state in your application using the 2-way `.sync` modifier.
12321300

1233-
**Note:** in order to allow `<b-table>` fully track it's `busy` state, custom items
1301+
**Note:** in order to allow `<b-table>` fully track it's `busy` state, the custom items
12341302
provider function should handle errors from data sources and return an empty
12351303
array to `<b-table>`.
12361304

1237-
`<b-table>` provides a `busy` prop that will flag the table as busy, which you can
1238-
set to `true` just before your async fetch, and then set it to `false` once you have
1239-
your data, and just before you send it to the table for display. Example:
1240-
1305+
**Example: usage of busy state**
12411306
```html
1242-
<b-table id="my-table" :busy.sync="isBusy" :items="myProvider" :fields="fields" ...></b-table>
1307+
<b-table id="my-table"
1308+
:busy.sync="isBusy"
1309+
:items="myProvider"
1310+
:fields="fields" ...>
1311+
</b-table>
12431312
```
1244-
12451313
```js
12461314
data () {
12471315
return {
@@ -1250,8 +1318,8 @@ data () {
12501318
}
12511319
methods: {
12521320
myProvider (ctx) {
1253-
// Here we don't set isBusy prop, so busy state will be handled by table itself
1254-
// this.isBusy = true
1321+
// Here we don't set isBusy prop, so busy state will be handled by table itself
1322+
// this.isBusy = true
12551323
let promise = axios.get('/some/url')
12561324

12571325
return promise.then((data) => {
@@ -1276,6 +1344,7 @@ __not__ be called/refreshed until the `busy` state has been set to `false`.
12761344
emitted when in the `busy` state (either set automatically during provider update,
12771345
or when manually set).
12781346

1347+
12791348
### Provider Paging, Filtering, and Sorting
12801349
By default, the items provider function is responsible for **all paging, filtering, and sorting**
12811350
of the data, before passing it to `b-table` for display.

src/components/table/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@
157157
"name": "table-colgroup",
158158
"description": "Slot to place custom colgroup and col elements"
159159
},
160+
{
161+
"name": "table-busy",
162+
"description": "Optional slot to place loading message when table is in the busy state"
163+
},
160164
{
161165
"name": "[field]",
162166
"description": "Scoped slot for custom data rendering of field data. See docs for scoped data"
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import Table from './table'
2+
import { mount } from '@vue/test-utils'
3+
4+
const testItems = [
5+
{ a: 1, b: 2, c: 3 },
6+
{ a: 5, b: 5, c: 6 },
7+
{ a: 7, b: 8, c: 9 }
8+
]
9+
10+
describe('b-table busy state', async () => {
11+
it('default should have attribute aria-busy=false', async () => {
12+
const wrapper = mount(Table, {
13+
propsData: {
14+
items: testItems
15+
}
16+
})
17+
expect(wrapper.attributes('aria-busy')).toBeDefined()
18+
expect(wrapper.attributes('aria-busy')).toEqual('false')
19+
})
20+
21+
it('default should have item rows rendered', async () => {
22+
const wrapper = mount(Table, {
23+
propsData: {
24+
items: testItems
25+
}
26+
})
27+
expect(wrapper.find('tbody').exists()).toBe(true)
28+
expect(wrapper.find('tbody').findAll('tr').exists()).toBe(true)
29+
expect(wrapper.find('tbody').findAll('tr').length).toBe(testItems.length)
30+
})
31+
32+
it('should have attribute aria-busy=true when prop busy=true', async () => {
33+
const wrapper = mount(Table, {
34+
propsData: {
35+
busy: true,
36+
items: testItems
37+
}
38+
})
39+
expect(wrapper.attributes('aria-busy')).toBeDefined()
40+
expect(wrapper.attributes('aria-busy')).toEqual('true')
41+
})
42+
43+
it('should have attribute aria-busy=true when data localBusy=true', async () => {
44+
const wrapper = mount(Table, {
45+
propsData: {
46+
items: testItems
47+
}
48+
})
49+
expect(wrapper.attributes('aria-busy')).toBeDefined()
50+
expect(wrapper.attributes('aria-busy')).toEqual('false')
51+
52+
wrapper.setData({
53+
localBusy: true
54+
})
55+
56+
expect(wrapper.attributes('aria-busy')).toBeDefined()
57+
expect(wrapper.attributes('aria-busy')).toEqual('true')
58+
})
59+
60+
it('should emit update:busy event when data localBusy is toggled', async () => {
61+
const wrapper = mount(Table, {
62+
propsData: {
63+
items: testItems
64+
}
65+
})
66+
expect(wrapper.emitted('update:busy')).not.toBeDefined()
67+
68+
wrapper.setData({
69+
localBusy: true
70+
})
71+
72+
expect(wrapper.emitted('update:busy')).toBeDefined()
73+
expect(wrapper.emitted('update:busy')[0][0]).toEqual(true)
74+
})
75+
76+
it('should render table-busy slot when prop busy=true and slot provided', async () => {
77+
const wrapper = mount(Table, {
78+
propsData: {
79+
busy: false,
80+
items: testItems
81+
},
82+
slots: {
83+
// Note slot data needs to be wrapped in an element.
84+
// https://github.com/vue/vue-test-utils/issues:992
85+
// Will be fixed in v1.0.0-beta.26
86+
'table-busy': '<span>busy slot content</span>'
87+
}
88+
})
89+
expect(wrapper.attributes('aria-busy')).toBeDefined()
90+
expect(wrapper.attributes('aria-busy')).toEqual('false')
91+
expect(wrapper.find('tbody').exists()).toBe(true)
92+
expect(wrapper.find('tbody').findAll('tr').exists()).toBe(true)
93+
expect(wrapper.find('tbody').findAll('tr').length).toBe(testItems.length)
94+
95+
wrapper.setProps({
96+
busy: true
97+
})
98+
99+
expect(wrapper.attributes('aria-busy')).toBeDefined()
100+
expect(wrapper.attributes('aria-busy')).toEqual('true')
101+
expect(wrapper.find('tbody').exists()).toBe(true)
102+
expect(wrapper.find('tbody').findAll('tr').exists()).toBe(true)
103+
expect(wrapper.find('tbody').findAll('tr').length).toBe(1)
104+
expect(wrapper.find('tbody').text()).toContain('busy slot content')
105+
expect(wrapper.find('tbody').find('tr').classes()).toContain('b-table-busy-slot')
106+
107+
wrapper.setProps({
108+
busy: false
109+
})
110+
111+
expect(wrapper.attributes('aria-busy')).toBeDefined()
112+
expect(wrapper.attributes('aria-busy')).toEqual('false')
113+
expect(wrapper.find('tbody').exists()).toBe(true)
114+
expect(wrapper.find('tbody').findAll('tr').exists()).toBe(true)
115+
expect(wrapper.find('tbody').findAll('tr').length).toBe(testItems.length)
116+
})
117+
})

0 commit comments

Comments
 (0)