Skip to content

Commit

Permalink
feat(table): Add table-busy slot for loading status. Closes #1859 (#2196
Browse files Browse the repository at this point in the history
)

* 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
  • Loading branch information
tmorehouse committed Nov 19, 2018
1 parent e0cdca0 commit a654a61
Show file tree
Hide file tree
Showing 4 changed files with 325 additions and 111 deletions.
93 changes: 81 additions & 12 deletions src/components/table/README.md
Expand Up @@ -609,6 +609,73 @@ elements are limited. Refer to [MDN](https://developer.mozilla.org/en-US/docs/We
for details and usage of `<colgroup>`


## Table busy state
`<b-table>` provides a `busy` prop that will flag the table as busy, which you can
set to `true` just before you update your items, and then set it to `false` once you have
your items. When in hte busy state, the tabe will have the attribute `aria-busy="true"`.

During the busy state, the table will be rendered in a "muted" look (`opacity: 0.6`), using the
following custom CSS:

```css
/* Busy table styling */
table.b-table[aria-busy='false'] {
opacity: 1;
}
table.b-table[aria-busy='true'] {
opacity: 0.6;
}
```

You can override this styling using your own CSS.

You may optionally provide a `table-busy` slot to show a custom loading message or spinner
whenever the table's busy state is `true`. The slot will be placed in a `<tr>` element with
class `b-table-busy-slot`, which has one single `<td>` with a `colspan` set to the number of fields.

**Example of `table-busy` slot usage:**
```html
<template>
<div>
<b-button @click="toggleBusy">Toggle Busy State</b-button>
<b-table :items="items" :busy="isBusy" class="mt-3" outlined>
<div slot="table-busy" class="text-center text-danger">
<br><strong>Loading...</strong><br><br>
</div>
</b-table>
</div>
</template>
<script>
export default {
data() {
return {
isBusy: false,
items: [
{ first_name: 'Dickerson', last_name: 'MacDonald', age: 40 },
{ first_name: 'Larsen', last_name: 'Shaw', age: 21 },
{ first_name: 'Geneva', last_name: 'Wilson', age: 89 },
{ first_name: 'Jami', last_name: 'Carney',age: 38 }
]
}
},
methods: {
toggleBusy() {
this.isBusy = !this.isBusy
}
}
}
</script>

<!-- table-busy-slot.vue -->
```

Also see the [**Using Items Provider Functions**](#using-items-provider-functions) below for additional
informaton on the `busy` state.

**Note:** All click related and hover events, and sort-changed events will __not__ be
emitted when the table is in the `busy` state.


## Custom Data Rendering
Custom rendering for each data field in a row is possible using either
[scoped slots](http://vuejs.org/v2/guide/components.html#Scoped-Slots)
Expand Down Expand Up @@ -1226,22 +1293,23 @@ function myProvider (ctx) {
}
```

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

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

`<b-table>` provides a `busy` prop that will flag the table as busy, which you can
set to `true` just before your async fetch, and then set it to `false` once you have
your data, and just before you send it to the table for display. Example:

**Example: usage of busy state**
```html
<b-table id="my-table" :busy.sync="isBusy" :items="myProvider" :fields="fields" ...></b-table>
<b-table id="my-table"
:busy.sync="isBusy"
:items="myProvider"
:fields="fields" ...>
</b-table>
```

```js
data () {
return {
Expand All @@ -1250,8 +1318,8 @@ data () {
}
methods: {
myProvider (ctx) {
// Here we don't set isBusy prop, so busy state will be handled by table itself
// this.isBusy = true
// Here we don't set isBusy prop, so busy state will be handled by table itself
// this.isBusy = true
let promise = axios.get('/some/url')

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


### Provider Paging, Filtering, and Sorting
By default, the items provider function is responsible for **all paging, filtering, and sorting**
of the data, before passing it to `b-table` for display.
Expand Down
4 changes: 4 additions & 0 deletions src/components/table/package.json
Expand Up @@ -157,6 +157,10 @@
"name": "table-colgroup",
"description": "Slot to place custom colgroup and col elements"
},
{
"name": "table-busy",
"description": "Optional slot to place loading message when table is in the busy state"
},
{
"name": "[field]",
"description": "Scoped slot for custom data rendering of field data. See docs for scoped data"
Expand Down
117 changes: 117 additions & 0 deletions src/components/table/table-busy.spec.js
@@ -0,0 +1,117 @@
import Table from './table'
import { mount } from '@vue/test-utils'

const testItems = [
{ a: 1, b: 2, c: 3 },
{ a: 5, b: 5, c: 6 },
{ a: 7, b: 8, c: 9 }
]

describe('b-table busy state', async () => {
it('default should have attribute aria-busy=false', async () => {
const wrapper = mount(Table, {
propsData: {
items: testItems
}
})
expect(wrapper.attributes('aria-busy')).toBeDefined()
expect(wrapper.attributes('aria-busy')).toEqual('false')
})

it('default should have item rows rendered', async () => {
const wrapper = mount(Table, {
propsData: {
items: testItems
}
})
expect(wrapper.find('tbody').exists()).toBe(true)
expect(wrapper.find('tbody').findAll('tr').exists()).toBe(true)
expect(wrapper.find('tbody').findAll('tr').length).toBe(testItems.length)
})

it('should have attribute aria-busy=true when prop busy=true', async () => {
const wrapper = mount(Table, {
propsData: {
busy: true,
items: testItems
}
})
expect(wrapper.attributes('aria-busy')).toBeDefined()
expect(wrapper.attributes('aria-busy')).toEqual('true')
})

it('should have attribute aria-busy=true when data localBusy=true', async () => {
const wrapper = mount(Table, {
propsData: {
items: testItems
}
})
expect(wrapper.attributes('aria-busy')).toBeDefined()
expect(wrapper.attributes('aria-busy')).toEqual('false')

wrapper.setData({
localBusy: true
})

expect(wrapper.attributes('aria-busy')).toBeDefined()
expect(wrapper.attributes('aria-busy')).toEqual('true')
})

it('should emit update:busy event when data localBusy is toggled', async () => {
const wrapper = mount(Table, {
propsData: {
items: testItems
}
})
expect(wrapper.emitted('update:busy')).not.toBeDefined()

wrapper.setData({
localBusy: true
})

expect(wrapper.emitted('update:busy')).toBeDefined()
expect(wrapper.emitted('update:busy')[0][0]).toEqual(true)
})

it('should render table-busy slot when prop busy=true and slot provided', async () => {
const wrapper = mount(Table, {
propsData: {
busy: false,
items: testItems
},
slots: {
// Note slot data needs to be wrapped in an element.
// https://github.com/vue/vue-test-utils/issues:992
// Will be fixed in v1.0.0-beta.26
'table-busy': '<span>busy slot content</span>'
}
})
expect(wrapper.attributes('aria-busy')).toBeDefined()
expect(wrapper.attributes('aria-busy')).toEqual('false')
expect(wrapper.find('tbody').exists()).toBe(true)
expect(wrapper.find('tbody').findAll('tr').exists()).toBe(true)
expect(wrapper.find('tbody').findAll('tr').length).toBe(testItems.length)

wrapper.setProps({
busy: true
})

expect(wrapper.attributes('aria-busy')).toBeDefined()
expect(wrapper.attributes('aria-busy')).toEqual('true')
expect(wrapper.find('tbody').exists()).toBe(true)
expect(wrapper.find('tbody').findAll('tr').exists()).toBe(true)
expect(wrapper.find('tbody').findAll('tr').length).toBe(1)
expect(wrapper.find('tbody').text()).toContain('busy slot content')
expect(wrapper.find('tbody').find('tr').classes()).toContain('b-table-busy-slot')

wrapper.setProps({
busy: false
})

expect(wrapper.attributes('aria-busy')).toBeDefined()
expect(wrapper.attributes('aria-busy')).toEqual('false')
expect(wrapper.find('tbody').exists()).toBe(true)
expect(wrapper.find('tbody').findAll('tr').exists()).toBe(true)
expect(wrapper.find('tbody').findAll('tr').length).toBe(testItems.length)
})
})

0 comments on commit a654a61

Please sign in to comment.