Skip to content
Permalink
Browse files

feat(b-table): add `selectRow()` and `unselectRow()` methods to cell …

…and row-details slot scopes, and new prop `no-select-on-click` (#4283)
  • Loading branch information
tmorehouse committed Oct 21, 2019
1 parent 1246916 commit 64b881fe2fa19c65a806af85b83504290277557c
@@ -894,6 +894,8 @@ The slot's scope variable (`data` in the above sample) will have the following p
| `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 |
| `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 |
| `rowSelected` | Boolean | Will be `true` if the row has been selected. See section [Row select support](#row-select-support) for additional information |
| `selectRow` | Function | When called, selects the current row. See section [Row select support](#row-select-support) for additional information |
| `unselectRow` | Function | When called, unselects the current row. See section [Row select support](#row-select-support) for additional information |

**Notes:**

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

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

| Property | Type | Description |
| --------------- | -------- | ------------------------------------------------------------------------- |
| `item` | Object | The entire row record data object |
| `index` | Number | The current visible row number |
| `fields` | Array | The normalized fields definition array (in the _array of objects_ format) |
| `toggleDetails` | Function | Function to toggle visibility of the row's details slot |
| Property | Type | Description |
| --------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `item` | Object | The entire row record data object |
| `index` | Number | The current visible row number |
| `fields` | Array | The normalized fields definition array (in the _array of objects_ format) |
| `toggleDetails` | Function | Function to toggle visibility of the row's details slot |
| `rowSelected` | Boolean | Will be `true` if the row has been selected. See section [Row select support](#row-select-support) for additional information |
| `selectRow` | Function | When called, selects the current row. See section [Row select support](#row-select-support) for additional information |
| `unselectRow` | Function | When called, unselects the current row. See section [Row select support](#row-select-support) for additional information |

Note: the row select related scope properties are only available in `<b-table>`.

In the following example, we show two methods of toggling the visibility of the details: one via a
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
- In `single` mode, `selectRow(index)` will unselect any previous selected row.
- Attempting to `selectRow(index)` or `unselectRow(index)` on a non-existent row will be ignored.
- The table must be `selectable` for any of these methods to have effect.
- You can disable selection of rows via click events by setting the `no-select-on-click` prop. Rows
will then only be selectable programmatically.

**Row select notes:**

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

### Data row accessibility

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

When the table items rows are placed in the document tab sequence (`<b-table>` and
`<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
- <kbd>END</kbd> or <kbd>DOWN</kbd>+<kbd>SHIFT</kbd> will move to the last row
- <kbd>HOME</kbd> or <kbd>UP</kbd>+<kbd>SHIFT</kbd> will move to the first row
- <kbd>ENTER</kbd> or <kbd>SPACE</kbd> to click the row.
- <kbd>SHIFT</kbd> and <kbd>CTRL</kbd> modifiers will also work (depending on the table selectable
mode, for `<b-table>` only).

### Row event accessibility

@@ -361,7 +361,7 @@ $bv-escaped-characters: (("<", "%3c"), (">", "%3e"), ("#", "%23"));

// --- Selectable rows ---
.table.b-table {
&.b-table-selectable {
&.b-table-selectable:not(.b-table-selectable-no-click) {
& > tbody > tr {
cursor: pointer;
}
@@ -19,6 +19,11 @@ export default {
selectedVariant: {
type: String,
default: () => getComponentConfig('BTable', 'selectedVariant')
},
noSelectOnClick: {
// Disable use of click handlers for row selection
type: Boolean,
default: false
}
},
data() {
@@ -31,6 +36,12 @@ export default {
isSelectable() {
return this.selectable && this.selectMode
},
hasSelectableRowClick() {
return this.isSelectable && !this.noSelectOnClick
},
supportsSelectableRows() {
return true
},
selectableHasSelection() {
return (
this.isSelectable &&
@@ -46,11 +57,15 @@ export default {
return {
'b-table-selectable': this.isSelectable,
[`b-table-select-${this.selectMode}`]: this.isSelectable,
'b-table-selecting': this.selectableHasSelection
'b-table-selecting': this.selectableHasSelection,
'b-table-selectable-no-click': this.isSelectable && !this.hasSelectableRowClick
}
},
selectableTableAttrs() {
return {
// TODO:
// Should this attribute not be included when no-select-on-click is set
// since this attribute implies keyboard navigation?
'aria-multiselectable': !this.isSelectable
? null
: this.selectableIsMultiSelect
@@ -82,6 +97,10 @@ export default {
selectMode(newVal, oldVal) {
this.clearSelected()
},
hasSelectableRowClick(newVal, oldVal) {
this.clearSelected()
this.setSelectionHandlers(!newVal)
},
selectedRows(selectedRows, oldVal) {
if (this.isSelectable && !looseEqual(selectedRows, oldVal)) {
const items = []
@@ -96,7 +115,7 @@ export default {
}
},
beforeMount() {
// Set up handlers
// Set up handlers if needed
if (this.isSelectable) {
this.setSelectionHandlers(true)
}
@@ -161,7 +180,7 @@ export default {
}
},
setSelectionHandlers(on) {
const method = on ? '$on' : '$off'
const method = on && !this.noSelectOnClick ? '$on' : '$off'
// Handle row-clicked event
this[method]('row-clicked', this.selectionHandler)
// Clear selection on filter, pagination, and sort changes
@@ -170,11 +189,9 @@ export default {
},
selectionHandler(item, index, evt) {
/* istanbul ignore if: should never happen */
if (!this.isSelectable) {
if (!this.isSelectable || this.noSelectOnClick) {
// Don't do anything if table is not in selectable mode
/* istanbul ignore next: should never happen */
this.clearSelected()
/* istanbul ignore next: should never happen */
return
}
const selectMode = this.selectMode
@@ -146,9 +146,12 @@ export default {
toggleDetails: this.toggleDetailsFactory(hasDetailsSlot, item),
detailsShowing: Boolean(item._showDetails)
}
if (this.selectedRows) {
// Add in rowSelected scope property if selectable rows supported
// If table supports selectable mode, then add in the following scope
// this.supportsSelectableRows will be undefined if mixin isn't loaded
if (this.supportsSelectableRows) {
slotScope.rowSelected = this.isRowSelected(rowIndex)
slotScope.selectRow = () => this.selectRow(rowIndex)
slotScope.unselectRow = () => this.unselectRow(rowIndex)
}
// The new `v-slot` syntax doesn't like a slot name starting with
// a square bracket and if using in-document HTML templates, the
@@ -174,7 +177,7 @@ export default {
const tableStriped = this.striped
const hasDetailsSlot = this.hasNormalizedSlot(detailsSlotName)
const rowShowDetails = Boolean(item._showDetails && hasDetailsSlot)
const hasRowClickHandler = this.$listeners['row-clicked'] || this.isSelectable
const hasRowClickHandler = this.$listeners['row-clicked'] || this.hasSelectableRowClick

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

// Render the details slot in a TD
const $details = h(BTd, { props: { colspan: fields.length }, class: this.detailsTdClass }, [
@@ -133,7 +133,7 @@ export default {
const items = this.computedItems
// Shortcut to `createElement` (could use `this._c()` instead)
const h = this.$createElement
const hasRowClickHandler = this.$listeners['row-clicked'] || this.isSelectable
const hasRowClickHandler = this.$listeners['row-clicked'] || this.hasSelectableRowClick

// Prepare the tbody rows
const $rows = []
@@ -252,6 +252,11 @@
"prop": "selectedVariant",
"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"
},
{
"prop": "noSelectOnClick",
"version": "2.1.0",
"description": "Disables row selection via click events. Row selection will be only available programmatically"
},
{
"prop": "showEmpty",
"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 @@
{
"prop": "rowSelected",
"type": "Boolean",
"description": "Will be true if the row has been selected."
"description": "Will be true if the row has been selected. Only applicable when table is in selectable mode"
},
{
"prop": "selectRow",
"type": "Function",
"version": "2.1.0",
"description": "Can be called to select the current row. Only applicable when table is in selectable mode"
},
{
"prop": "unselectRow",
"type": "Function",
"version": "2.1.0",
"description": "Can be called to unselect the current row. Only applicable when table is in selectable mode"
}
]
},
@@ -595,7 +612,19 @@
{
"prop": "rowSelected",
"type": "Boolean",
"description": "Will be true if the row has been selected."
"description": "Will be true if the row has been selected. Only applicable when table is in selectable mode"
},
{
"prop": "selectRow",
"type": "Function",
"version": "2.1.0",
"description": "Can be called to select the current row. Only applicable when table is in selectable mode"
},
{
"prop": "unselectRow",
"type": "Function",
"version": "2.1.0",
"description": "Can be called to unselect the current row. Only applicable when table is in selectable mode"
}
]
},
@@ -782,6 +811,24 @@
"prop": "toggleDetails",
"type": "Function",
"description": "Function to toggle visibility of the row's details slot"
},
{
"prop": "rowSelected",
"type": "Boolean",
"version": "2.1.0",
"description": "Will be true if the row has been selected. Only applicable when table is in selectable mode"
},
{
"prop": "selectRow",
"type": "Function",
"version": "2.1.0",
"description": "Can be called to select the current row. Only applicable when table is in selectable mode"
},
{
"prop": "unselectRow",
"type": "Function",
"version": "2.1.0",
"description": "Can be called to unselect the current row. Only applicable when table is in selectable mode"
}
]
},
@@ -31,6 +31,7 @@ describe('table > row select', () => {
await waitNT(wrapper.vm)
expect(wrapper.attributes('aria-multiselectable')).not.toBeDefined()
expect(wrapper.classes()).not.toContain('b-table-selectable')
expect(wrapper.classes()).not.toContain('b-table-selectable-no-click')
expect(wrapper.classes()).not.toContain('b-table-selecting')
expect(wrapper.classes()).not.toContain('b-table-select-single')
expect(wrapper.classes()).not.toContain('b-table-select-multi')
@@ -59,6 +60,7 @@ describe('table > row select', () => {
await waitNT(wrapper.vm)
expect(wrapper.attributes('aria-multiselectable')).not.toBeDefined()
expect(wrapper.classes()).not.toContain('b-table-selectable')
expect(wrapper.classes()).not.toContain('b-table-selectable-no-click')
expect(wrapper.classes()).not.toContain('b-table-selecting')
expect(wrapper.classes()).not.toContain('b-table-select-single')
expect(wrapper.classes()).not.toContain('b-table-select-multi')
@@ -73,6 +75,31 @@ describe('table > row select', () => {
wrapper.destroy()
})

it('has class b-table-selectable-no-click when prop no-select-on-click set', async () => {
const wrapper = mount(BTable, {
propsData: {
fields: testFields,
items: testItems,
selectable: true,
selectMode: 'single',
noSelectOnClick: true
}
})

expect(wrapper).toBeDefined()
await waitNT(wrapper.vm)
expect(wrapper.attributes('aria-multiselectable')).toBe('false')
expect(wrapper.classes()).toContain('b-table-selectable')
expect(wrapper.classes()).toContain('b-table-select-single')
expect(wrapper.classes()).toContain('b-table-selectable-no-click')
expect(wrapper.classes()).not.toContain('b-table-selecting')
expect(wrapper.classes()).not.toContain('b-table-select-multi')
expect(wrapper.classes()).not.toContain('b-table-select-range')
expect(wrapper.emitted('row-selected')).not.toBeDefined()

wrapper.destroy()
})

it('select mode single works', async () => {
const wrapper = mount(BTable, {
propsData: {
@@ -89,6 +116,7 @@ describe('table > row select', () => {
expect(wrapper.attributes('aria-multiselectable')).toBe('false')
expect(wrapper.classes()).toContain('b-table-selectable')
expect(wrapper.classes()).toContain('b-table-select-single')
expect(wrapper.classes()).not.toContain('b-table-selectable-no-click')
expect(wrapper.classes()).not.toContain('b-table-selecting')
expect(wrapper.classes()).not.toContain('b-table-select-multi')
expect(wrapper.classes()).not.toContain('b-table-select-range')

0 comments on commit 64b881f

Please sign in to comment.
You can’t perform that action at this time.