diff --git a/src/components/table/README.md b/src/components/table/README.md index b82bbf18abe..cb44a086ecd 100644 --- a/src/components/table/README.md +++ b/src/components/table/README.md @@ -1168,6 +1168,28 @@ Slot `thead-top` can be optionally scoped, receiving an object with the followin | `selectAllRows` | Method | Select all rows (applicable if the table is in [`selectable`](#row-select-support) mode | | `clearSelected` | Method | Unselect all rows (applicable if the table is in [`selectable`](#row-select-support) mode | +### Creating a custom footer + +If you need greater layout control of the content of the ``, you can use the optionally +scoped slot `custom-foot` to provide your own rows and cells. Use BootstrapVue's +[table helper sub-components](#table-helper-components) ``, ``, and `` to generate +your custom footer layout. + +Slot `custom-foot` can be optionally scoped, receiving an object with the following properties: + +| Property | Type | Description | +| --------- | ------ | ------------------------------------------------------------------------------------------ | +| `columns` | Number | The number of columns in the rendered table | +| `fields` | Array | Array of field definition objects (normalized to the array of objects format) | +| `items` | Array | Array of the currently _displayed_ items records - after filtering, sorting and pagination | + +**Notes:** + +- The `custom-foot` slot will **not** be rendered if the `foot-clone` prop has been set. +- `head-clicked` events are not be emitted when clicking on `custom-foot` cells. +- Sorting and sorting icons are not available for cells in the `custom-foot` slot. +- The custom footer will not be shown when the table is in visually stacked mode. + ## Custom empty and emptyfiltered rendering via slots Aside from using `empty-text`, `empty-filtered-text`, `empty-html`, and `empty-filtered-html`, it is @@ -2654,8 +2676,8 @@ helper components. `TableSimplePlugin` is available as a top level named export. ## Table helper components BootstrapVue provides additional helper child components when using ``, or the named -slots `top-row`, `bottom-row`, and `thead-top` (all of which accept table child elements). The -helper components are as follows: +slots `top-row`, `bottom-row`, `thead-top`, and `custom-foot` (all of which accept table child +elements). The helper components are as follows: - `b-tbody` (`` only) - `b-thead` (`` only) diff --git a/src/components/table/helpers/mixin-tfoot.js b/src/components/table/helpers/mixin-tfoot.js index bb2ef94c097..e8555d14842 100644 --- a/src/components/table/helpers/mixin-tfoot.js +++ b/src/components/table/helpers/mixin-tfoot.js @@ -1,4 +1,5 @@ import { getComponentConfig } from '../../../utils/config' +import { BTfoot } from '../tfoot' export default { props: { @@ -20,11 +21,29 @@ export default { } }, methods: { - renderTfoot() { + renderTFootCustom() { const h = this.$createElement - + if (this.hasNormalizedSlot('custom-foot')) { + return h( + BTfoot, + { + key: 'bv-tfoot-custom', + class: this.tfootClass || null, + props: { footVariant: this.footVariant || this.headVariant || null } + }, + this.normalizeSlot('custom-foot', { + items: this.computedItems.slice(), + fields: this.computedFields.slice(), + columns: this.computedFields.length + }) + ) + } else { + return h() + } + }, + renderTfoot() { // Passing true to renderThead will make it render a tfoot - return this.footClone ? this.renderThead(true) : h() + return this.footClone ? this.renderThead(true) : this.renderTFootCustom() } } } diff --git a/src/components/table/package.json b/src/components/table/package.json index 789de215683..2c2ba384483 100644 --- a/src/components/table/package.json +++ b/src/components/table/package.json @@ -132,7 +132,7 @@ }, { "event": "head-clicked", - "description": "Emitted when a header or footer cell is clicked.", + "description": "Emitted when a header or footer cell is clicked. Not applicable for 'custom-foot' slot.", "args": [ { "arg": "key", @@ -250,15 +250,19 @@ }, { "name": "thead-top", - "description": "Slot above the column headers in the `thead` element for user-supplied rows (optionally scoped: columns - number of TDs to provide, fields - array of field definition objects)" + "description": "Slot above the column headers in the `thead` element for user-supplied B-TR's with B-TH/B-TD (optionally scoped: columns - number of TDs to provide, fields - array of field definition objects)" }, { "name": "top-row", - "description": "Fixed top row slot for user supplied TD cells (Optionally scoped: columns - number of TDs to provide, fields - array of field definition objects)" + "description": "Fixed top row slot for user supplied B-TD cells (Optionally scoped: columns - number of B-TDs to provide, fields - array of field definition objects)" }, { "name": "bottom-row", - "description": "Fixed bottom row slot for user supplied TD cells (Optionally Scoped: columns - number of TDs to provide, fields - array of field definition objects)" + "description": "Fixed bottom row slot for user supplied B-TD cells (Optionally Scoped: columns - number of B-TDs to provide, fields - array of field definition objects)" + }, + { + "name": "custom-foot", + "description": "Custom footer content slot for user supplied B-TR, B-TH, B-TD (Optionally Scoped: columns - number columns, fields - array of field definition objects, items - array of currently displayed row items)" } ] }, @@ -375,7 +379,7 @@ }, { "event": "head-clicked", - "description": "Emitted when a header or footer cell is clicked.", + "description": "Emitted when a header or footer cell is clicked. Not applicable for 'custom-foot' slot.", "args": [ { "arg": "key", @@ -435,7 +439,11 @@ }, { "name": "thead-top", - "description": "Slot above the column headers in the `thead` element for user-supplied rows (optionally scoped: columns - number of TDs to provide, fields - array of field definition objects)" + "description": "Slot above the column headers in the `thead` element for user-supplied B-TR with B-TH/B-TD (optionally scoped: columns - number of TDs to provide, fields - array of field definition objects)" + }, + { + "name": "custom-foot", + "description": "Custom footer content slot for user supplied B-TR's with B-TH/B-TD (Optionally Scoped: columns - number columns, fields - array of field definition objects, items - array of currently displayed row items)" } ] }, diff --git a/src/components/table/table-tfoot-custom.spec.js b/src/components/table/table-tfoot-custom.spec.js new file mode 100644 index 00000000000..becb9a8617f --- /dev/null +++ b/src/components/table/table-tfoot-custom.spec.js @@ -0,0 +1,91 @@ +import { mount } from '@vue/test-utils' +import { BTable } from './table' + +const testItems = [{ a: 1, b: 2, c: 3 }] +const testFields = [{ key: 'a', label: 'A' }, { key: 'b', label: 'B' }, { key: 'c', label: 'C' }] + +describe('table > custom tfoot slot', () => { + it('should not render tfoot by default', async () => { + const wrapper = mount(BTable, { + propsData: { + fields: testFields, + items: testItems, + footClone: false + } + }) + expect(wrapper).toBeDefined() + expect(wrapper.is('table')).toBe(true) + expect(wrapper.find('thead').exists()).toBe(true) + expect(wrapper.find('tbody').exists()).toBe(true) + expect(wrapper.find('tfoot').exists()).toBe(false) + + wrapper.destroy() + }) + + it('should render custom-foot slot inside b-tfoot', async () => { + const wrapper = mount(BTable, { + propsData: { + fields: testFields, + items: testItems, + footClone: false + }, + slots: { + 'custom-foot': 'CUSTOM-FOOTER' + } + }) + expect(wrapper).toBeDefined() + expect(wrapper.is('table')).toBe(true) + expect(wrapper.find('thead').exists()).toBe(true) + expect(wrapper.find('tbody').exists()).toBe(true) + expect(wrapper.find('tfoot').exists()).toBe(true) + expect(wrapper.find('tfoot').text()).toContain('CUSTOM-FOOTER') + expect(wrapper.find('tfoot').classes().length).toBe(0) + + wrapper.destroy() + }) + + it('should not render custom-foot slot when foot-clone is true', async () => { + const wrapper = mount(BTable, { + propsData: { + fields: testFields, + items: testItems, + footClone: true + }, + slots: { + 'custom-foot': 'CUSTOM-FOOTER' + } + }) + expect(wrapper).toBeDefined() + expect(wrapper.is('table')).toBe(true) + expect(wrapper.find('thead').exists()).toBe(true) + expect(wrapper.find('tbody').exists()).toBe(true) + expect(wrapper.find('tfoot').exists()).toBe(true) + expect(wrapper.find('tfoot').text()).not.toContain('CUSTOM-FOOTER') + + wrapper.destroy() + }) + + it('should have foot-variant on custom-foot slot', async () => { + const wrapper = mount(BTable, { + propsData: { + fields: testFields, + items: testItems, + footClone: false, + footVariant: 'dark' + }, + slots: { + 'custom-foot': 'CUSTOM-FOOTER' + } + }) + expect(wrapper).toBeDefined() + expect(wrapper.is('table')).toBe(true) + expect(wrapper.find('thead').exists()).toBe(true) + expect(wrapper.find('tbody').exists()).toBe(true) + expect(wrapper.find('tfoot').exists()).toBe(true) + expect(wrapper.find('tfoot').text()).toContain('CUSTOM-FOOTER') + expect(wrapper.find('tfoot').classes()).toContain('thead-dark') + expect(wrapper.find('tfoot').classes().length).toBe(1) + + wrapper.destroy() + }) +})