From 7109dff9a203b7a6a4546b07a9ed59aee88d7aa0 Mon Sep 17 00:00:00 2001 From: Jon Funkhouser Date: Tue, 13 Aug 2019 12:02:09 -0700 Subject: [PATCH 1/6] feat(tables): add support for custom header attributes --- src/components/table/README.md | 1 + .../table/helpers/mixin-tbody-row.js | 17 ++++++- src/components/table/helpers/mixin-thead.js | 3 +- src/components/table/index.d.ts | 1 + src/components/table/table-lite.spec.js | 50 +++++++++++++++++++ src/components/table/table.spec.js | 50 +++++++++++++++++++ 6 files changed, 120 insertions(+), 2 deletions(-) diff --git a/src/components/table/README.md b/src/components/table/README.md index aa5b82ecc0c..32788e03f38 100644 --- a/src/components/table/README.md +++ b/src/components/table/README.md @@ -331,6 +331,7 @@ The following field properties are recognized: | `thStyle` | Object | JavaScript object representing CSS styles you would like to apply to the table ``/`` field ``. | | `variant` | String | Apply contextual class to all the `` **and** `` in the column - `active`, `success`, `info`, `warning`, `danger`. These variants map to classes `thead-${variant}` (in the header), `table-${variant}` (in the body), or `bg-${variant}` (when the prop `dark` is set). | | `tdAttr` | Object or Function | JavaScript object representing additional attributes to apply to the `` field `` cell. If custom attributes per cell are required, a callback function can be specified instead. | +| `thAttr` | Object or Function | JavaScript object representing additional attributes to apply to the field's ``/`` heading `` cell. If the field's `isRowHeader` is set to `true`, the attributes will also apply to the `` field `` cell. If custom attributes per cell are required, a callback function can be specified instead. | | `isRowHeader` | Boolean | When set to `true`, the field's item data cell will be rendered with `` rather than the default of ``. | | `stickyColumn` | Boolean | NEW in 2.0.0-rc.28 When set to `true`, and the table in in [responsive](#responsive-tables) mode or has [sticky headers](#sticky-headers), will cause the column to become fixed to the left when the table's horizontal scrollbar is scrolled. See [Sticky columns](#sticky-columns) for more details | diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index b8552faf5d8..cd00f569914 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -33,6 +33,19 @@ export default { } return defValue }, + getThValues(item, key, thValue, type, defValue) { + const parent = this.$parent + if (thValue) { + const value = get(item, key, '') + if (isFunction(thValue)) { + return thValue(value, key, item, type) + } else if (isString(thValue) && isFunction(parent[thValue])) { + return parent[thValue](value, key, item, type) + } + return thValue + } + return defValue + }, // Method to get the value for a field getFormattedValue(item, field) { const key = field.key @@ -169,7 +182,9 @@ export default { }, attrs: { 'aria-colindex': String(colIndex + 1), - ...this.getTdValues(item, key, field.tdAttr, {}) + ...(field.isRowHeader + ? this.getThValues(item, key, field.thAttr, 'body', {}) + : this.getTdValues(item, key, field.tdAttr, {})) } } const slotScope = { diff --git a/src/components/table/helpers/mixin-thead.js b/src/components/table/helpers/mixin-thead.js index dd7c859f30f..6f3e262ccdf 100644 --- a/src/components/table/helpers/mixin-thead.js +++ b/src/components/table/helpers/mixin-thead.js @@ -95,7 +95,8 @@ export default { title: field.headerTitle || null, 'aria-colindex': String(colIndex + 1), 'aria-label': ariaLabel, - ...sortAttrs + ...sortAttrs, + ...this.getThValues(null, field.key, field.thAttr, isFoot ? 'foot' : 'head', {}) }, on: handlers } diff --git a/src/components/table/index.d.ts b/src/components/table/index.d.ts index 5483ce42cdd..7269c60d5fd 100644 --- a/src/components/table/index.d.ts +++ b/src/components/table/index.d.ts @@ -151,6 +151,7 @@ export interface BvTableField { thStyle?: any variant?: BvTableVariant | string tdAttr?: any | ((value: any, key: string, item: any) => any) + thAttr?: any | ((value: any, key: string, item: any, type: string) => any) isRowHeader?: boolean } diff --git a/src/components/table/table-lite.spec.js b/src/components/table/table-lite.spec.js index 5a16dd1c2b9..5a7778d3a49 100644 --- a/src/components/table/table-lite.spec.js +++ b/src/components/table/table-lite.spec.js @@ -589,6 +589,56 @@ describe('table-lite', () => { wrapper.destroy() }) + it('item field thAttr works', async () => { + const Parent = { + methods: { + parentThAttrs(value, key, item, type) { + return { 'data-type': type } + } + } + } + + const wrapper = mount(BTableLite, { + parentComponent: Parent, + propsData: { + items: [{ a: 1, b: 2, c: 3 }], + fields: [ + { key: 'a', thAttr: { 'data-foo': 'bar' } }, + { key: 'b', thAttr: 'parentThAttrs', isRowHeader: true }, + { key: 'c', thAttr: 'parentThAttrs' } + ] + } + }) + + expect(wrapper).toBeDefined() + expect(wrapper.findAll('thead > tr').length).toBe(1) + expect(wrapper.findAll('thead > tr > th').length).toBe(3) + expect(wrapper.findAll('tbody > tr').length).toBe(1) + expect(wrapper.findAll('tbody > tr > td').length).toBe(2) + expect(wrapper.findAll('tbody > tr > th').length).toBe(1) + + const $headerThs = wrapper.findAll('thead > tr > th') + expect($headerThs.at(0).attributes('data-foo')).toBe('bar') + expect($headerThs.at(0).attributes('data-type')).not.toBeDefined() + expect($headerThs.at(0).classes().length).toBe(0) + + expect($headerThs.at(1).attributes('data-foo')).not.toBeDefined() + expect($headerThs.at(1).attributes('data-type')).toBe('head') + expect($headerThs.at(1).classes().length).toBe(0) + + expect($headerThs.at(2).attributes('data-foo')).not.toBeDefined() + expect($headerThs.at(2).attributes('data-type')).toBe('head') + expect($headerThs.at(2).classes().length).toBe(0) + + const $bodyThs = wrapper.findAll('tbody > tr > th') + + expect($bodyThs.at(0).attributes('data-foo')).not.toBeDefined() + expect($bodyThs.at(0).attributes('data-type')).toBe('body') + expect($bodyThs.at(0).classes().length).toBe(0) + + wrapper.destroy() + }) + it('item field formatter as function works', async () => { const wrapper = mount(BTableLite, { propsData: { diff --git a/src/components/table/table.spec.js b/src/components/table/table.spec.js index 1bd90bb63e1..c1ef15674bb 100644 --- a/src/components/table/table.spec.js +++ b/src/components/table/table.spec.js @@ -632,4 +632,54 @@ describe('table', () => { wrapper.destroy() }) + + it('item field thAttr works', async () => { + const Parent = { + methods: { + parentThAttrs(value, key, item, type) { + return { 'data-type': type } + } + } + } + + const wrapper = mount(BTable, { + parentComponent: Parent, + propsData: { + items: [{ a: 1, b: 2, c: 3 }], + fields: [ + { key: 'a', thAttr: { 'data-foo': 'bar' } }, + { key: 'b', thAttr: 'parentThAttrs', isRowHeader: true }, + { key: 'c', thAttr: 'parentThAttrs' } + ] + } + }) + + expect(wrapper).toBeDefined() + expect(wrapper.findAll('thead > tr').length).toBe(1) + expect(wrapper.findAll('thead > tr > th').length).toBe(3) + expect(wrapper.findAll('tbody > tr').length).toBe(1) + expect(wrapper.findAll('tbody > tr > td').length).toBe(2) + expect(wrapper.findAll('tbody > tr > th').length).toBe(1) + + const $headerThs = wrapper.findAll('thead > tr > th') + expect($headerThs.at(0).attributes('data-foo')).toBe('bar') + expect($headerThs.at(0).attributes('data-type')).not.toBeDefined() + expect($headerThs.at(0).classes().length).toBe(0) + + expect($headerThs.at(1).attributes('data-foo')).not.toBeDefined() + expect($headerThs.at(1).attributes('data-type')).toBe('head') + expect($headerThs.at(1).classes().length).toBe(0) + + expect($headerThs.at(2).attributes('data-foo')).not.toBeDefined() + expect($headerThs.at(2).attributes('data-type')).toBe('head') + expect($headerThs.at(2).classes().length).toBe(0) + + const $bodyThs = wrapper.findAll('tbody > tr > th') + + expect($bodyThs.at(0).attributes('data-foo')).not.toBeDefined() + expect($bodyThs.at(0).attributes('data-type')).toBe('body') + expect($bodyThs.at(0).classes().length).toBe(0) + + wrapper.destroy() + }) }) From 96a3137e9a3ce5cb53105e4935b77c97b03a83f3 Mon Sep 17 00:00:00 2001 From: Jon Funkhouser Date: Tue, 13 Aug 2019 13:35:37 -0700 Subject: [PATCH 2/6] Update README.md --- src/components/table/README.md | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/components/table/README.md b/src/components/table/README.md index 32788e03f38..8d4769188b7 100644 --- a/src/components/table/README.md +++ b/src/components/table/README.md @@ -314,26 +314,26 @@ typically be in the order they were defined in the object, although **field orde The following field properties are recognized: -| Property | Type | Description | -| ------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `key` | String | The key for selecting data from the record in the items array. Required when setting the `fields` via an array of objects. | -| `label` | String | Appears in the columns table header (and footer if `foot-clone` is set). Defaults to the field's key (in humanized format) if not provided. It's possible to use empty labels by assigning an empty string `""` but be sure you also set `headerTitle` to provide non-sighted users a hint about the column contents. | -| `headerTitle` | String | Text to place on the fields header `` attribute `title`. Defaults to no `title` attribute. | -| `headerAbbr` | String | Text to place on the fields header `` attribute `abbr`. Set this to the unabbreviated version of the label (or title) if label (or title) is an abbreviation. Defaults to no `abbr` attribute. | -| `class` | String or Array | Class name (or array of class names) to add to `` **and** `` in the column. | -| `formatter` | String or Function | A formatter callback function or name of a method in your component, can be used instead of (or in conjunction with) scoped field slots. Refer to [Custom Data Rendering](#custom-data-rendering) for more details. | -| `sortable` | Boolean | Enable sorting on this column. Refer to the [Sorting](#sorting) Section for more details. | -| `sortDirection` | String | Set the initial sort direction on this column when it becomes sorted. Refer to the [Change initial sort direction](#Change-initial-sort-direction) Section for more details. | -| `sortByFormatted` | Boolean | NEW in 2.0.0-rc.28 Sort the column by the result of the field's `formatter` callback function. Default is `false`. Has no effect if the field does not have a `formatter`. Refer to the [Sorting](#sorting) Section for more details. | -| `filterByFormatted` | Boolean | NEW in 2.0.0-rc.28 Filter the column by the result of the field's `formatter` callback function. Default is `false`. Has no effect if the field does not have a `formatter`. Refer to the [Filtering](#filtering) section for more details. | -| `tdClass` | String or Array or Function | Class name (or array of class names) to add to `` data `` cells in the column. If custom classes per cell are required, a callback function can be specified instead. | -| `thClass` | String or Array | Class name (or array of class names) to add to this field's ``/`` heading `` cell. | -| `thStyle` | Object | JavaScript object representing CSS styles you would like to apply to the table ``/`` field ``. | -| `variant` | String | Apply contextual class to all the `` **and** `` in the column - `active`, `success`, `info`, `warning`, `danger`. These variants map to classes `thead-${variant}` (in the header), `table-${variant}` (in the body), or `bg-${variant}` (when the prop `dark` is set). | -| `tdAttr` | Object or Function | JavaScript object representing additional attributes to apply to the `` field `` cell. If custom attributes per cell are required, a callback function can be specified instead. | -| `thAttr` | Object or Function | JavaScript object representing additional attributes to apply to the field's ``/`` heading `` cell. If the field's `isRowHeader` is set to `true`, the attributes will also apply to the `` field `` cell. If custom attributes per cell are required, a callback function can be specified instead. | -| `isRowHeader` | Boolean | When set to `true`, the field's item data cell will be rendered with `` rather than the default of ``. | -| `stickyColumn` | Boolean | NEW in 2.0.0-rc.28 When set to `true`, and the table in in [responsive](#responsive-tables) mode or has [sticky headers](#sticky-headers), will cause the column to become fixed to the left when the table's horizontal scrollbar is scrolled. See [Sticky columns](#sticky-columns) for more details | +| Property | Type | Description | +| ------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `key` | String | The key for selecting data from the record in the items array. Required when setting the `fields` via an array of objects. | +| `label` | String | Appears in the columns table header (and footer if `foot-clone` is set). Defaults to the field's key (in humanized format) if not provided. It's possible to use empty labels by assigning an empty string `""` but be sure you also set `headerTitle` to provide non-sighted users a hint about the column contents. | +| `headerTitle` | String | Text to place on the fields header `` attribute `title`. Defaults to no `title` attribute. | +| `headerAbbr` | String | Text to place on the fields header `` attribute `abbr`. Set this to the unabbreviated version of the label (or title) if label (or title) is an abbreviation. Defaults to no `abbr` attribute. | +| `class` | String or Array | Class name (or array of class names) to add to `` **and** `` in the column. | +| `formatter` | String or Function | A formatter callback function or name of a method in your component, can be used instead of (or in conjunction with) scoped field slots. Refer to [Custom Data Rendering](#custom-data-rendering) for more details. | +| `sortable` | Boolean | Enable sorting on this column. Refer to the [Sorting](#sorting) Section for more details. | +| `sortDirection` | String | Set the initial sort direction on this column when it becomes sorted. Refer to the [Change initial sort direction](#Change-initial-sort-direction) Section for more details. | +| `sortByFormatted` | Boolean | NEW in 2.0.0-rc.28 Sort the column by the result of the field's `formatter` callback function. Default is `false`. Has no effect if the field does not have a `formatter`. Refer to the [Sorting](#sorting) Section for more details. | +| `filterByFormatted` | Boolean | NEW in 2.0.0-rc.28 Filter the column by the result of the field's `formatter` callback function. Default is `false`. Has no effect if the field does not have a `formatter`. Refer to the [Filtering](#filtering) section for more details. | +| `tdClass` | String or Array or Function | Class name (or array of class names) to add to `` data `` cells in the column. If custom classes per cell are required, a callback function can be specified instead. The function will be called as `tdClass( value, key, item )` and it may return an `Array` or `String`. | +| `thClass` | String or Array | Class name (or array of class names) to add to this field's ``/`` heading `` cell. | +| `thStyle` | Object | JavaScript object representing CSS styles you would like to apply to the table ``/`` field ``. | +| `variant` | String | Apply contextual class to all the `` **and** `` in the column - `active`, `success`, `info`, `warning`, `danger`. These variants map to classes `thead-${variant}` (in the header), `table-${variant}` (in the body), or `bg-${variant}` (when the prop `dark` is set). | +| `tdAttr` | Object or Function | JavaScript object representing additional attributes to apply to the `` field `` cell. If custom attributes per cell are required, a callback function can be specified instead. The function will be called as `tdAttr( value, key, item )` and it may return an `Object`. | +| `thAttr` | Object or Function | JavaScript object representing additional attributes to apply to the field's ``/`` heading `` cell. If the field's `isRowHeader` is set to `true`, the attributes will also apply to the `` field `` cell. If custom attributes per cell are required, a callback function can be specified instead. The function will be called as `thAttr( value, key, item, type )` and it may return an `Object`. | +| `isRowHeader` | Boolean | When set to `true`, the field's item data cell will be rendered with `` rather than the default of ``. | +| `stickyColumn` | Boolean | NEW in 2.0.0-rc.28 When set to `true`, and the table in in [responsive](#responsive-tables) mode or has [sticky headers](#sticky-headers), will cause the column to become fixed to the left when the table's horizontal scrollbar is scrolled. See [Sticky columns](#sticky-columns) for more details | **Notes:** From 5b419a0b02c45ee2f3d8195122feac3c2f094321 Mon Sep 17 00:00:00 2001 From: Jon Funkhouser Date: Tue, 13 Aug 2019 13:36:39 -0700 Subject: [PATCH 3/6] Update mixin-tbody-row.js --- src/components/table/helpers/mixin-tbody-row.js | 2 +- src/components/table/table-lite.spec.js | 2 +- src/components/table/table.spec.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index cd00f569914..5e5d446d288 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -183,7 +183,7 @@ export default { attrs: { 'aria-colindex': String(colIndex + 1), ...(field.isRowHeader - ? this.getThValues(item, key, field.thAttr, 'body', {}) + ? this.getThValues(item, key, field.thAttr, 'row', {}) : this.getTdValues(item, key, field.tdAttr, {})) } } diff --git a/src/components/table/table-lite.spec.js b/src/components/table/table-lite.spec.js index 5a7778d3a49..4d03b57bd1d 100644 --- a/src/components/table/table-lite.spec.js +++ b/src/components/table/table-lite.spec.js @@ -633,7 +633,7 @@ describe('table-lite', () => { const $bodyThs = wrapper.findAll('tbody > tr > th') expect($bodyThs.at(0).attributes('data-foo')).not.toBeDefined() - expect($bodyThs.at(0).attributes('data-type')).toBe('body') + expect($bodyThs.at(0).attributes('data-type')).toBe('row') expect($bodyThs.at(0).classes().length).toBe(0) wrapper.destroy() diff --git a/src/components/table/table.spec.js b/src/components/table/table.spec.js index c1ef15674bb..066394e1ef5 100644 --- a/src/components/table/table.spec.js +++ b/src/components/table/table.spec.js @@ -677,7 +677,7 @@ describe('table', () => { const $bodyThs = wrapper.findAll('tbody > tr > th') expect($bodyThs.at(0).attributes('data-foo')).not.toBeDefined() - expect($bodyThs.at(0).attributes('data-type')).toBe('body') + expect($bodyThs.at(0).attributes('data-type')).toBe('row') expect($bodyThs.at(0).classes().length).toBe(0) wrapper.destroy() From e706c60a9f13ecbe2fbf94c5966bd130b39177e8 Mon Sep 17 00:00:00 2001 From: Jon Funkhouser Date: Tue, 13 Aug 2019 13:36:51 -0700 Subject: [PATCH 4/6] Update mixin-thead.js --- src/components/table/helpers/mixin-thead.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/table/helpers/mixin-thead.js b/src/components/table/helpers/mixin-thead.js index 6f3e262ccdf..88218c3c95d 100644 --- a/src/components/table/helpers/mixin-thead.js +++ b/src/components/table/helpers/mixin-thead.js @@ -95,8 +95,8 @@ export default { title: field.headerTitle || null, 'aria-colindex': String(colIndex + 1), 'aria-label': ariaLabel, - ...sortAttrs, - ...this.getThValues(null, field.key, field.thAttr, isFoot ? 'foot' : 'head', {}) + ...this.getThValues(null, field.key, field.thAttr, isFoot ? 'foot' : 'head', {}), + ...sortAttrs }, on: handlers } From 4be32f712223b58a86481e97ab9f57f0db3e9d2d Mon Sep 17 00:00:00 2001 From: Jon Funkhouser Date: Tue, 13 Aug 2019 13:42:24 -0700 Subject: [PATCH 5/6] Update table.spec.js --- src/components/table/table.spec.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/table/table.spec.js b/src/components/table/table.spec.js index 066394e1ef5..332ef0a7e6a 100644 --- a/src/components/table/table.spec.js +++ b/src/components/table/table.spec.js @@ -649,7 +649,12 @@ describe('table', () => { fields: [ { key: 'a', thAttr: { 'data-foo': 'bar' } }, { key: 'b', thAttr: 'parentThAttrs', isRowHeader: true }, - { key: 'c', thAttr: 'parentThAttrs' } + { + key: 'c', + thAttr: (v, k, i, t) => { + return { 'data-type': t } + } + } ] } }) From 273bd15bbb504e883a1f03f41f4f0e0fe7b6fe7a Mon Sep 17 00:00:00 2001 From: Jon Funkhouser Date: Tue, 13 Aug 2019 13:42:37 -0700 Subject: [PATCH 6/6] Update table-lite.spec.js --- src/components/table/table-lite.spec.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/table/table-lite.spec.js b/src/components/table/table-lite.spec.js index 4d03b57bd1d..98417c5e0fb 100644 --- a/src/components/table/table-lite.spec.js +++ b/src/components/table/table-lite.spec.js @@ -605,7 +605,12 @@ describe('table-lite', () => { fields: [ { key: 'a', thAttr: { 'data-foo': 'bar' } }, { key: 'b', thAttr: 'parentThAttrs', isRowHeader: true }, - { key: 'c', thAttr: 'parentThAttrs' } + { + key: 'c', + thAttr: (v, k, i, t) => { + return { 'data-type': t } + } + } ] } })