Skip to content

Commit 64b881f

Browse files
authored
feat(b-table): add selectRow() and unselectRow() methods to cell and row-details slot scopes, and new prop no-select-on-click (bootstrap-vue#4283)
1 parent 1246916 commit 64b881f

File tree

7 files changed

+135
-25
lines changed

7 files changed

+135
-25
lines changed

src/components/table/README.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,8 @@ The slot's scope variable (`data` in the above sample) will have the following p
894894
| `detailsShowing` | Boolean | Will be `true` if the row's `row-details` scoped slot is visible. See section [Row details support](#row-details-support) below for additional information |
895895
| `toggleDetails` | Function | Can be called to toggle the visibility of the rows `row-details` scoped slot. See section [Row details support](#row-details-support) below for additional information |
896896
| `rowSelected` | Boolean | Will be `true` if the row has been selected. See section [Row select support](#row-select-support) for additional information |
897+
| `selectRow` | Function | When called, selects the current row. See section [Row select support](#row-select-support) for additional information |
898+
| `unselectRow` | Function | When called, unselects the current row. See section [Row select support](#row-select-support) for additional information |
897899

898900
**Notes:**
899901

@@ -1407,12 +1409,17 @@ for proper reactive detection of changes to it's value. Read more about
14071409

14081410
**Available `row-details` scoped variable properties:**
14091411

1410-
| Property | Type | Description |
1411-
| --------------- | -------- | ------------------------------------------------------------------------- |
1412-
| `item` | Object | The entire row record data object |
1413-
| `index` | Number | The current visible row number |
1414-
| `fields` | Array | The normalized fields definition array (in the _array of objects_ format) |
1415-
| `toggleDetails` | Function | Function to toggle visibility of the row's details slot |
1412+
| Property | Type | Description |
1413+
| --------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
1414+
| `item` | Object | The entire row record data object |
1415+
| `index` | Number | The current visible row number |
1416+
| `fields` | Array | The normalized fields definition array (in the _array of objects_ format) |
1417+
| `toggleDetails` | Function | Function to toggle visibility of the row's details slot |
1418+
| `rowSelected` | Boolean | Will be `true` if the row has been selected. See section [Row select support](#row-select-support) for additional information |
1419+
| `selectRow` | Function | When called, selects the current row. See section [Row select support](#row-select-support) for additional information |
1420+
| `unselectRow` | Function | When called, unselects the current row. See section [Row select support](#row-select-support) for additional information |
1421+
1422+
Note: the row select related scope properties are only available in `<b-table>`.
14161423

14171424
In the following example, we show two methods of toggling the visibility of the details: one via a
14181425
button, and one via a checkbox. We also have the third row details defaulting to have details
@@ -1510,6 +1517,8 @@ Rows can also be programmatically selected and unselected via the following expo
15101517
- In `single` mode, `selectRow(index)` will unselect any previous selected row.
15111518
- Attempting to `selectRow(index)` or `unselectRow(index)` on a non-existent row will be ignored.
15121519
- The table must be `selectable` for any of these methods to have effect.
1520+
- You can disable selection of rows via click events by setting the `no-select-on-click` prop. Rows
1521+
will then only be selectable programmatically.
15131522

15141523
**Row select notes:**
15151524

@@ -2757,10 +2766,11 @@ cells.
27572766

27582767
### Data row accessibility
27592768

2760-
When the table is in `selectable` mode (`<b-table>` only), or if there is a `row-clicked` event
2761-
listener registered (`<b-table>` and `<b-table-lite>`), all data item rows (`<tr>` elements) will be
2762-
placed into the document tab sequence (via `tabindex="0"`) to allow keyboard-only and screen reader
2763-
users the ability to click the rows by pressing <kbd>ENTER</kbd>.
2769+
When the table is in `selectable` mode (`<b-table>` only, and prop `no-select-on-click` is not set),
2770+
or if there is a `row-clicked` event listener registered (`<b-table>` and `<b-table-lite>`), all
2771+
data item rows (`<tr>` elements) will be placed into the document tab sequence (via `tabindex="0"`)
2772+
to allow keyboard-only and screen reader users the ability to click the rows by pressing
2773+
<kbd>ENTER</kbd> or <kbd>SPACE</kbd>.
27642774

27652775
When the table items rows are placed in the document tab sequence (`<b-table>` and
27662776
`<b-table-lite>`), they will also support basic keyboard navigation when focused:
@@ -2770,8 +2780,6 @@ When the table items rows are placed in the document tab sequence (`<b-table>` a
27702780
- <kbd>END</kbd> or <kbd>DOWN</kbd>+<kbd>SHIFT</kbd> will move to the last row
27712781
- <kbd>HOME</kbd> or <kbd>UP</kbd>+<kbd>SHIFT</kbd> will move to the first row
27722782
- <kbd>ENTER</kbd> or <kbd>SPACE</kbd> to click the row.
2773-
- <kbd>SHIFT</kbd> and <kbd>CTRL</kbd> modifiers will also work (depending on the table selectable
2774-
mode, for `<b-table>` only).
27752783

27762784
### Row event accessibility
27772785

src/components/table/_table.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ $bv-escaped-characters: (("<", "%3c"), (">", "%3e"), ("#", "%23"));
361361

362362
// --- Selectable rows ---
363363
.table.b-table {
364-
&.b-table-selectable {
364+
&.b-table-selectable:not(.b-table-selectable-no-click) {
365365
& > tbody > tr {
366366
cursor: pointer;
367367
}

src/components/table/helpers/mixin-selectable.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export default {
1919
selectedVariant: {
2020
type: String,
2121
default: () => getComponentConfig('BTable', 'selectedVariant')
22+
},
23+
noSelectOnClick: {
24+
// Disable use of click handlers for row selection
25+
type: Boolean,
26+
default: false
2227
}
2328
},
2429
data() {
@@ -31,6 +36,12 @@ export default {
3136
isSelectable() {
3237
return this.selectable && this.selectMode
3338
},
39+
hasSelectableRowClick() {
40+
return this.isSelectable && !this.noSelectOnClick
41+
},
42+
supportsSelectableRows() {
43+
return true
44+
},
3445
selectableHasSelection() {
3546
return (
3647
this.isSelectable &&
@@ -46,11 +57,15 @@ export default {
4657
return {
4758
'b-table-selectable': this.isSelectable,
4859
[`b-table-select-${this.selectMode}`]: this.isSelectable,
49-
'b-table-selecting': this.selectableHasSelection
60+
'b-table-selecting': this.selectableHasSelection,
61+
'b-table-selectable-no-click': this.isSelectable && !this.hasSelectableRowClick
5062
}
5163
},
5264
selectableTableAttrs() {
5365
return {
66+
// TODO:
67+
// Should this attribute not be included when no-select-on-click is set
68+
// since this attribute implies keyboard navigation?
5469
'aria-multiselectable': !this.isSelectable
5570
? null
5671
: this.selectableIsMultiSelect
@@ -82,6 +97,10 @@ export default {
8297
selectMode(newVal, oldVal) {
8398
this.clearSelected()
8499
},
100+
hasSelectableRowClick(newVal, oldVal) {
101+
this.clearSelected()
102+
this.setSelectionHandlers(!newVal)
103+
},
85104
selectedRows(selectedRows, oldVal) {
86105
if (this.isSelectable && !looseEqual(selectedRows, oldVal)) {
87106
const items = []
@@ -96,7 +115,7 @@ export default {
96115
}
97116
},
98117
beforeMount() {
99-
// Set up handlers
118+
// Set up handlers if needed
100119
if (this.isSelectable) {
101120
this.setSelectionHandlers(true)
102121
}
@@ -161,7 +180,7 @@ export default {
161180
}
162181
},
163182
setSelectionHandlers(on) {
164-
const method = on ? '$on' : '$off'
183+
const method = on && !this.noSelectOnClick ? '$on' : '$off'
165184
// Handle row-clicked event
166185
this[method]('row-clicked', this.selectionHandler)
167186
// Clear selection on filter, pagination, and sort changes
@@ -170,11 +189,9 @@ export default {
170189
},
171190
selectionHandler(item, index, evt) {
172191
/* istanbul ignore if: should never happen */
173-
if (!this.isSelectable) {
192+
if (!this.isSelectable || this.noSelectOnClick) {
174193
// Don't do anything if table is not in selectable mode
175-
/* istanbul ignore next: should never happen */
176194
this.clearSelected()
177-
/* istanbul ignore next: should never happen */
178195
return
179196
}
180197
const selectMode = this.selectMode

src/components/table/helpers/mixin-tbody-row.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,12 @@ export default {
146146
toggleDetails: this.toggleDetailsFactory(hasDetailsSlot, item),
147147
detailsShowing: Boolean(item._showDetails)
148148
}
149-
if (this.selectedRows) {
150-
// Add in rowSelected scope property if selectable rows supported
149+
// If table supports selectable mode, then add in the following scope
150+
// this.supportsSelectableRows will be undefined if mixin isn't loaded
151+
if (this.supportsSelectableRows) {
151152
slotScope.rowSelected = this.isRowSelected(rowIndex)
153+
slotScope.selectRow = () => this.selectRow(rowIndex)
154+
slotScope.unselectRow = () => this.unselectRow(rowIndex)
152155
}
153156
// The new `v-slot` syntax doesn't like a slot name starting with
154157
// a square bracket and if using in-document HTML templates, the
@@ -174,7 +177,7 @@ export default {
174177
const tableStriped = this.striped
175178
const hasDetailsSlot = this.hasNormalizedSlot(detailsSlotName)
176179
const rowShowDetails = Boolean(item._showDetails && hasDetailsSlot)
177-
const hasRowClickHandler = this.$listeners['row-clicked'] || this.isSelectable
180+
const hasRowClickHandler = this.$listeners['row-clicked'] || this.hasSelectableRowClick
178181

179182
// We can return more than one TR if rowDetails enabled
180183
const $rows = []
@@ -253,6 +256,13 @@ export default {
253256
fields: fields,
254257
toggleDetails: this.toggleDetailsFactory(hasDetailsSlot, item)
255258
}
259+
// If table supports selectable mode, then add in the following scope
260+
// this.supportsSelectableRows will be undefined if mixin isn't loaded
261+
if (this.supportsSelectableRows) {
262+
detailsScope.rowSelected = this.isRowSelected(rowIndex)
263+
detailsScope.selectRow = () => this.selectRow(rowIndex)
264+
detailsScope.unselectRow = () => this.unselectRow(rowIndex)
265+
}
256266

257267
// Render the details slot in a TD
258268
const $details = h(BTd, { props: { colspan: fields.length }, class: this.detailsTdClass }, [

src/components/table/helpers/mixin-tbody.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export default {
133133
const items = this.computedItems
134134
// Shortcut to `createElement` (could use `this._c()` instead)
135135
const h = this.$createElement
136-
const hasRowClickHandler = this.$listeners['row-clicked'] || this.isSelectable
136+
const hasRowClickHandler = this.$listeners['row-clicked'] || this.hasSelectableRowClick
137137

138138
// Prepare the tbody rows
139139
const $rows = []

src/components/table/package.json

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,11 @@
252252
"prop": "selectedVariant",
253253
"description": "Bootstrap color theme variant to set selected rows to. Use any of the standard Bootstrap theme color variants, or the special table row variant 'active' (default). Set to an empty string to not use a variant"
254254
},
255+
{
256+
"prop": "noSelectOnClick",
257+
"version": "2.1.0",
258+
"description": "Disables row selection via click events. Row selection will be only available programmatically"
259+
},
255260
{
256261
"prop": "showEmpty",
257262
"description": "When enabled, and there are no item records to show, shows a message that there are no rows to show"
@@ -551,7 +556,19 @@
551556
{
552557
"prop": "rowSelected",
553558
"type": "Boolean",
554-
"description": "Will be true if the row has been selected."
559+
"description": "Will be true if the row has been selected. Only applicable when table is in selectable mode"
560+
},
561+
{
562+
"prop": "selectRow",
563+
"type": "Function",
564+
"version": "2.1.0",
565+
"description": "Can be called to select the current row. Only applicable when table is in selectable mode"
566+
},
567+
{
568+
"prop": "unselectRow",
569+
"type": "Function",
570+
"version": "2.1.0",
571+
"description": "Can be called to unselect the current row. Only applicable when table is in selectable mode"
555572
}
556573
]
557574
},
@@ -595,7 +612,19 @@
595612
{
596613
"prop": "rowSelected",
597614
"type": "Boolean",
598-
"description": "Will be true if the row has been selected."
615+
"description": "Will be true if the row has been selected. Only applicable when table is in selectable mode"
616+
},
617+
{
618+
"prop": "selectRow",
619+
"type": "Function",
620+
"version": "2.1.0",
621+
"description": "Can be called to select the current row. Only applicable when table is in selectable mode"
622+
},
623+
{
624+
"prop": "unselectRow",
625+
"type": "Function",
626+
"version": "2.1.0",
627+
"description": "Can be called to unselect the current row. Only applicable when table is in selectable mode"
599628
}
600629
]
601630
},
@@ -782,6 +811,24 @@
782811
"prop": "toggleDetails",
783812
"type": "Function",
784813
"description": "Function to toggle visibility of the row's details slot"
814+
},
815+
{
816+
"prop": "rowSelected",
817+
"type": "Boolean",
818+
"version": "2.1.0",
819+
"description": "Will be true if the row has been selected. Only applicable when table is in selectable mode"
820+
},
821+
{
822+
"prop": "selectRow",
823+
"type": "Function",
824+
"version": "2.1.0",
825+
"description": "Can be called to select the current row. Only applicable when table is in selectable mode"
826+
},
827+
{
828+
"prop": "unselectRow",
829+
"type": "Function",
830+
"version": "2.1.0",
831+
"description": "Can be called to unselect the current row. Only applicable when table is in selectable mode"
785832
}
786833
]
787834
},

src/components/table/table-selectable.spec.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('table > row select', () => {
3131
await waitNT(wrapper.vm)
3232
expect(wrapper.attributes('aria-multiselectable')).not.toBeDefined()
3333
expect(wrapper.classes()).not.toContain('b-table-selectable')
34+
expect(wrapper.classes()).not.toContain('b-table-selectable-no-click')
3435
expect(wrapper.classes()).not.toContain('b-table-selecting')
3536
expect(wrapper.classes()).not.toContain('b-table-select-single')
3637
expect(wrapper.classes()).not.toContain('b-table-select-multi')
@@ -59,6 +60,7 @@ describe('table > row select', () => {
5960
await waitNT(wrapper.vm)
6061
expect(wrapper.attributes('aria-multiselectable')).not.toBeDefined()
6162
expect(wrapper.classes()).not.toContain('b-table-selectable')
63+
expect(wrapper.classes()).not.toContain('b-table-selectable-no-click')
6264
expect(wrapper.classes()).not.toContain('b-table-selecting')
6365
expect(wrapper.classes()).not.toContain('b-table-select-single')
6466
expect(wrapper.classes()).not.toContain('b-table-select-multi')
@@ -73,6 +75,31 @@ describe('table > row select', () => {
7375
wrapper.destroy()
7476
})
7577

78+
it('has class b-table-selectable-no-click when prop no-select-on-click set', async () => {
79+
const wrapper = mount(BTable, {
80+
propsData: {
81+
fields: testFields,
82+
items: testItems,
83+
selectable: true,
84+
selectMode: 'single',
85+
noSelectOnClick: true
86+
}
87+
})
88+
89+
expect(wrapper).toBeDefined()
90+
await waitNT(wrapper.vm)
91+
expect(wrapper.attributes('aria-multiselectable')).toBe('false')
92+
expect(wrapper.classes()).toContain('b-table-selectable')
93+
expect(wrapper.classes()).toContain('b-table-select-single')
94+
expect(wrapper.classes()).toContain('b-table-selectable-no-click')
95+
expect(wrapper.classes()).not.toContain('b-table-selecting')
96+
expect(wrapper.classes()).not.toContain('b-table-select-multi')
97+
expect(wrapper.classes()).not.toContain('b-table-select-range')
98+
expect(wrapper.emitted('row-selected')).not.toBeDefined()
99+
100+
wrapper.destroy()
101+
})
102+
76103
it('select mode single works', async () => {
77104
const wrapper = mount(BTable, {
78105
propsData: {
@@ -89,6 +116,7 @@ describe('table > row select', () => {
89116
expect(wrapper.attributes('aria-multiselectable')).toBe('false')
90117
expect(wrapper.classes()).toContain('b-table-selectable')
91118
expect(wrapper.classes()).toContain('b-table-select-single')
119+
expect(wrapper.classes()).not.toContain('b-table-selectable-no-click')
92120
expect(wrapper.classes()).not.toContain('b-table-selecting')
93121
expect(wrapper.classes()).not.toContain('b-table-select-multi')
94122
expect(wrapper.classes()).not.toContain('b-table-select-range')

0 commit comments

Comments
 (0)