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()
+ })
+})