1# Table 2 3All tables in the application are using the 4[BoostrapVue table component](https://bootstrap-vue.org/docs/components/table). 5 6To use the component, include the `<b-table>` tag in the template. The component 7is registered globally so does not need to be imported in each SFC. 8 9## Basic table 10 11There are a few required properties to maintain consistency across the 12application. The full list of options can be viewed on the 13[Bootstrap-vue table component's documentation page](https://bootstrap-vue.org/docs/components/table#comp-ref-b-table-props). 14 15### Required properties 16 17- `items` - renders table items 18- `fields` - renders table header 19- `hover` - enables table row hover state 20- `responsive` or `stacked` - makes the table responsive (enables horizontal 21 scrolling or stacked view) at the defined breakpoint 22- `show-empty` _(required if table data is generated dynamically)_ - shows an 23 empty message if there are no items in the table 24- `empty-text` _(required if table data is generated dynamically)_ - the 25 translated empty message 26 27![Basic table example](./table.png) 28![Basic empty table 29example](./table-empty.png) 30 31```vue 32<template> 33 <b-table 34 hover 35 show-empty 36 responsive="md" 37 :items="items" 38 :fields="fields" 39 :empty-text="$t('global.table.emptyMessage')" 40 /> 41</template> 42 43<script> 44export default { 45 data() { 46 items: [ 47 { 48 name: 'Babe', 49 age: '3 years', 50 color: 'white, orange, grey' 51 }, 52 { 53 name: 'Grey Boy', 54 age: '4 months', 55 color: 'grey' 56 }, 57 ], 58 fields: [ 59 { 60 key: 'name', 61 label: this.$t('table.name') //translated label 62 }, 63 { 64 key: 'age', 65 label: this.$t('table.age') //translated label 66 }, 67 { 68 key: 'color', 69 label: this.$t('table.color') // translated label 70 } 71 ] 72 } 73} 74</script> 75``` 76 77## Sort 78 79To enable table sort, include `sortable: true` in the fields array for sortable 80columns and add the following props to the `<b-table>` component: 81 82- `sort-by` 83- `no-sort-reset` 84- `sort-icon-left` 85 86![Table sort example](./table-sort.png) 87 88```vue 89<template> 90 <b-table 91 hover 92 no-sort-reset 93 sort-icon-left 94 sort-by="rank" 95 responsive="md" 96 :items="items" 97 :fields="fields" 98 /> 99</template> 100<script> 101export default { 102 data() { 103 return { 104 items: [...], 105 fields: [ 106 { 107 key: 'name', 108 label: 'Name', //should be translated 109 sortable: true 110 }, 111 { 112 key: 'rank', 113 label: 'Rank', //should be translated 114 sortable: true 115 }, 116 { 117 key: 'description', 118 label: 'Description', //should be translated 119 sortable: false 120 } 121 ] 122 } 123 } 124} 125</script> 126``` 127 128## Expandable rows 129 130To add an expandable row in the table, add a column for the expand button in the 131fields array. Include the tdClass `table-row-expand` to ensure icon rotation is 132handled. Use the built in 133[cell slot](https://bootstrap-vue.org/docs/components/table#comp-ref-b-table-slots) 134to target the expand button column and add a button with the chevron icon. 135 136Include the 137[TableRowExpandMixin](https://github.com/openbmc/webui-vue/blob/master/src/components/Mixins/TableRowExpandMixin.js). 138The mixin contains the dynamic `aria-label` and `title` attribute values that 139need to be included with the expand button. The `toggleRowDetails` method should 140be the button's click event callback. Be sure to pass the `row` object to the 141function. 142 143Use the 144[row-details slot](https://bootstrap-vue.org/docs/components/table#comp-ref-b-table-slots) 145to format the expanded row content. The slot has access to the row `item` 146property. 147 148### Summary 149 1501. Add a column for the expansion row button with the tdClass, 151 `table-row-expand` 1522. Include the `TableRowExpandMixin` to handle the dynamic aria label, title, 153 and row expansion toggling 1543. Use the `#cell` slot to target the expandable row column and add the button 155 with accessible markup and click handler 1564. Use the `#row-details` slot to format expanded row content 157 158![Table row expand example](./table-expand-row.png) 159 160```vue 161<template> 162 <b-table hover responsive="md" :items="items" :fields="fields"> 163 <template #cell(expandRow)="row"> 164 <b-button 165 variant="link" 166 :aria-label="expandRowLabel" 167 :title="expandRowLabel" 168 @click="toggleRowDetails(row)" 169 > 170 <icon-chevron /> 171 </b-button> 172 </template> 173 <template #row-details="row"> 174 <h3>Expanded row details</h3> 175 {{ row.item }} 176 </template> 177 </b-table> 178</template> 179<script> 180import IconChevron from '@carbon/icons-vue/es/chevron--down/20'; 181import TableRowExpandMixin, { expandRowLabel } from '@/components/Mixins/TableRowExpandMixin'; 182 183export default { 184 components: { IconChevron }, 185 mixins: [ TableRowExpandMixin ], 186 data() { 187 return { 188 items: [...], 189 fields: [ 190 { 191 key: 'expandRow', 192 label: '', 193 tdClass: 'table-row-expand', 194 }, 195 ... 196 ], 197 expandRowLabel 198 } 199 } 200} 201</script> 202``` 203 204## Search 205 206The table is leveraging 207[BootstrapVue table filtering](https://bootstrap-vue.org/docs/components/table#filtering) 208for search. Add the 209[@filtered](https://bootstrap-vue.org/docs/components/table#filter-events) event 210listener onto the `<b-table>` component. The event callback should track the 211total filtered items count. 212 213Import the `<search>` and `<table-cell-count>` components and include them in 214the template above the `<b-table>` component. 215 216Include the 217[SearchFilterMixin](https://github.com/openbmc/webui-vue/blob/master/src/components/Mixins/SearchFilterMixin.js). 218Add the `@change-search` and `@clear-search` event listeners on the `<search>` 219component and use the corresponding `onChangeSearchInput` and 220`onClearSearchInput` methods as the event callbacks. The table should also 221include the dynamic `:filter` prop with `searchFilter` set as the value. 222 223The `<table-cell-count>` component requires two properties, total table item 224count and total filtered items count. 225 226Add the `:empty-filtered-text` prop to the table to show the translated message 227if there are no search matches. 228 229![Table search example](./table-search.png) 230 231![Table search active example](./table-search-active.png) 232 233![Table search empty example](./table-search-empty.png) 234 235```vue 236<template> 237 <b-container> 238 <b-row> 239 <b-col> 240 <search 241 @changeSearch="onChangeSearchInput" 242 @clearSearch="onClearSearchInput" 243 /> 244 </b-col> 245 <b-col> 246 <table-cell-count 247 :filtered-items-count="filteredItemsCount" 248 :total-number-of-cells="items.length" 249 /> 250 </b-col> 251 </b-row> 252 <b-table 253 hover 254 responsive="md" 255 :items="items" 256 :fields="fields" 257 :filter="searchFilter" 258 :empty-filtered-text="$t('global.table.emptySearchMessage')" 259 @filtered="onFiltered" 260 /> 261 </b-container> 262</template> 263<script> 264import Search from '@/components/Global/Search'; 265import TableCellCount from '@/components/Global/TableCellCount'; 266import SearchFilterMixin, { searchFilter } from '@/components/Mixins/SearchFilterMixin'; 267 268export default { 269 components: { Search, TableCellCount }, 270 mixins: [ SearchFilterMixin ], 271 data() { 272 return { 273 items: [...], 274 fields: [...], 275 searchFilter, 276 filteredItems: [], 277 } 278 }, 279 computed: { 280 filteredItemsCount() { 281 return this.filteredItems.length; 282 }, 283 }, 284 methods: { 285 onFiltered(items) { 286 this.filteredItems = items; 287 }, 288 }, 289} 290</script> 291``` 292 293## Row actions 294 295To add table row actions, add a column for the action buttons in the table. Then 296in the array of table items, add a corresponding array of actions for each item. 297The array should have each desired row action with a `value` and `title` 298property. 299 300Import the `<table-row-action>` component. Provide the `value` and `title` props 301to the component and use the named `#icons` slot to include an icon. The 302component will emit a `@click-table-action` with the event value. 303 304![Table row actions example](./table-row-actions.png) 305 306```vue 307<template> 308 <b-table 309 hover 310 responsive="md" 311 :items="itemsWithActions" 312 :fields="fields" 313 > 314 <template #cell(actions)="row"> 315 <table-row-action 316 v-for="(action, index) in row.item.actions" 317 :key="index" 318 :value="action.value" 319 :title="action.title" 320 @click-table-action="onTableRowAction($event, row.item)" 321 /> 322 <template #icon> 323 <icon-edit v-if="action.value === 'edit'"/> 324 <icon-delete v-if="action.value === 'delete'"/> 325 </template> 326 </table-row-action> 327 </template> 328 </b-table> 329</template> 330<script> 331import IconDelete from '@carbon/icons-vue/es/trash-can/20'; 332import IconEdit from '@carbon/icons-vue/es/edit/20'; 333import TableRowAction from '@/components/Global/TableRowAction'; 334 335export default { 336 components: { IconDelete, IconEdit, TableRowAction }, 337 data() { 338 return { 339 items: [...], 340 fields: [ 341 ..., 342 { 343 key: 'actions', 344 label: '', 345 tdClass: 'text-right text-nowrap', 346 } 347 ], 348 } 349 }, 350 computed: { 351 itemsWithActions() { 352 return this.items.map((item) => { 353 return { 354 ...item, 355 actions: [ 356 { 357 value: 'edit', 358 title: this.$t('global.action.edit'), 359 }, 360 { 361 value: 'delete', 362 title: this.$t('global.action.delete'), 363 }, 364 ], 365 }; 366 }); 367 } 368 }, 369 methods: { 370 onTableRowAction(event, row) { 371 // row action callback 372 } 373 } 374} 375</script> 376``` 377 378## Filters 379 380To add a table dropdown filter: 381 3821. Import the `<table-filter> `component and TableFilterMixin. 3831. Add a filters prop to the `<table-filters>` component. This prop should be an 384 array of filter groups–each required to have a key, label, and values prop. 385 386The `label` prop value should be the translated filter group label. The `key` 387prop will usually match the filtered by table column key. The `values` prop 388should be an array of filter values that will render as a list of checkbox items 389in the dropdown. 390 391The component will emit a `@filter-change` event that will provide the filter 392group and all selected values in the group. Use the getFilteredTableData method 393from the TableFilterMixin to show the filtered table data. 394 395![Table filter example](./table-filter.png) 396 397![Table filter active example](./table-filter-active.png) 398 399```vue 400<template> 401 <b-container> 402 <b-row> 403 <b-col class="text-right"> 404 <table-filter 405 :filters="tableFilters" 406 @filter-change="onTableFilterChange" 407 /> 408 </b-col> 409 </b-row> 410 <b-table hover responsive="md" :items="filteredItems" :fields="fields" /> 411 </b-container> 412</template> 413<script> 414import TableFilter from '@/components/Global/TableFilter'; 415import TableFilterMixin from '@/components/Mixins/TableFilterMixin'; 416 417export default { 418 components: { TableFilter }, 419 mixins: [ TableFilterMixin ], 420 data() { 421 return { 422 items: [...], 423 fields: [...], 424 tableFilters: [ 425 { 426 label: this.$t('table.status'), 427 key: status, 428 values: ['Open', 'Closed'] 429 } 430 ], 431 activeFilters: [], 432 }, 433 }, 434 computed: { 435 filteredItems() { 436 return this.getFilteredTableData(this.items, this.activeFilters); 437 }, 438 }, 439 methods: { 440 onTableFilterChange({ activeFilters }) { 441 this.activeFilters = activeFilters; 442 }, 443 }, 444} 445</script> 446``` 447 448### Date filter 449 450To add a date filter, import the `<table-date-filter>` component. It will emit a 451`@change` event with the user input date values. There is a date filter method, 452`getFilteredTableDataByDate`, in the `TableFilterMixin`. 453 454## Batch actions 455 456Batch actions allow a user to take a single action on many items in a table at 457once. 458 459To add table batch actions: 460 4611. Import the `<table-toolbar> `component and BVTableSelectableMixin 4621. Add the `selectable`, `no-select-on-click` props and a unique `ref` to the 463 table. The table will emit a `@row-selected` event. Use the `onRowSelected` 464 mixin method as a callback and provide the `$event` as the first argument and 465 the total table items count as the second argument. 4661. Add a table column for checkboxes. The table header checkbox should use the 467 `tableHeaderCheckboxModel` and `tableHeaderCheckboxIndeterminate` values 468 provided by the mixin. The table header checkbox should also use the 469 `onChangeHeaderCheckbox` method as a callback for the `@change` event with 470 the table `ref` passed as an argument. The table row checkboxes should use 471 the `toggleSelectRow` method as a callback for the `@change` event with the 472 table `ref` passed as the first argument and the row index passed as the 473 second argument. 4741. Add an actions prop to the `<table-toolbar>` component. This prop should be 475 an array of toolbar actions–required to have a value and label prop. Add the 476 `selected-items-count` prop to the `<table-toolbar>` component. The component 477 will emit a `@batch-action` event that will provide the user selected action. 478 It will also emit a `@clear-selected` event. Provide the `clearSelectedRows` 479 as a callback with the table `ref` passed as an argument. 480 481![Table batch action example](./table-batch-action.png) 482 483![Table batch action active example](./table-batch-action-active.png) 484 485```vue 486<template> 487 <b-container> 488 <table-toolbar 489 :selected-items-count="selectedRows.length" 490 :actions="tableToolbarActions" 491 @clear-selected="clearSelectedRows($refs.table)" 492 @batch-action="onBatchAction" 493 /> 494 <b-table 495 ref="table" 496 hover 497 selectable 498 no-select-on-click 499 responsive="md" 500 :items="filteredItems" 501 :fields="fields" 502 @row-selected="onRowSelected($event, items.length)" 503 > 504 <template #head(checkbox)> 505 <b-form-checkbox 506 v-model="tableHeaderCheckboxModel" 507 :indeterminate="tableHeaderCheckboxIndeterminate" 508 @change="onChangeHeaderCheckbox($refs.table)" 509 /> 510 </template> 511 <template #cell(checkbox)="row"> 512 <b-form-checkbox 513 v-model="row.rowSelected" 514 @change="toggleSelectRow($refs.table, row.index)" 515 /> 516 </template> 517 </b-table> 518 </b-container> 519</template> 520<script> 521import TableToolbar from '@/components/Global/TableToolbar'; 522import BVTableSelectableMixin, { 523 tableHeaderCheckboxModel, 524 tableHeaderCheckboxIndeterminate, 525 selectedRows 526} from '@/components/Mixins/BVTableSelectableMixin'; 527 528export default { 529 components: { TableToolbar }, 530 mixins: [ BVTableSelectableMixin ], 531 data() { 532 return { 533 items: [...], 534 fields: [ 535 { 536 key: 'checkbox' 537 }, 538 ... 539 ], 540 tableToolbarActions: [ 541 { 542 value: 'edit', 543 label: this.$t('global.action.edit') 544 }, 545 { 546 value: 'delete', 547 label: this.$t('global.action.delete') 548 }, 549 ], 550 tableHeaderCheckboxModel, 551 tableHeaderCheckboxIndeterminate, 552 selectedRows 553 }, 554 }, 555 methods: { 556 onBatchAction(action) { 557 // Do something with selected batch action and selected rows 558 }, 559 }, 560} 561</script> 562``` 563 564## Pagination 565 566To add table pagination: 567 5681. Import the BVPaginationMixin 5691. Add the `per-page` and `current-page` props to the `<table>` component. 5701. Add the below HTML snippet to the template. Make sure to update the 571 `total-rows` prop. 572 573```vue{21} 574<b-row> 575 <b-col sm="6"> 576 <b-form-group 577 class="table-pagination-select" 578 :label="$t('global.table.itemsPerPage')" 579 label-for="pagination-items-per-page" 580 > 581 <b-form-select 582 id="pagination-items-per-page" 583 v-model="perPage" 584 :options="itemsPerPageOptions" 585 /> 586 </b-form-group> 587 </b-col> 588 <b-col sm="6"> 589 <b-pagination 590 v-model="currentPage" 591 first-number 592 last-number 593 :per-page="perPage" 594 :total-rows="getTotalRowCount(items.length)" 595 aria-controls="table-event-logs" 596 /> 597 </b-col> 598</b-row> 599``` 600 601![Table pagination example](./table-pagination.png) 602 603```vue 604<template> 605 <b-container> 606 <b-table 607 hover 608 responsive="md" 609 :items="filteredItems" 610 :fields="fields" 611 :per-page="perPage" 612 :current-page="currentPage" 613 /> 614 <b-row> 615 <b-col sm="6"> 616 <b-form-group 617 class="table-pagination-select" 618 :label="$t('global.table.itemsPerPage')" 619 label-for="pagination-items-per-page" 620 > 621 <b-form-select 622 id="pagination-items-per-page" 623 v-model="perPage" 624 :options="itemsPerPageOptions" 625 /> 626 </b-form-group> 627 </b-col> 628 <b-col sm="6"> 629 <b-pagination 630 v-model="currentPage" 631 first-number 632 last-number 633 :per-page="perPage" 634 :total-rows="getTotalRowCount(items.length)" 635 aria-controls="table-event-logs" 636 /> 637 </b-col> 638 </b-row> 639 </b-container> 640</template> 641<script> 642import BVPaginationMixin, { 643 currentPage, 644 perPage, 645 itemsPerPageOptions 646} from '@/components/Mixins/BVPaginationMixin'; 647 648export default { 649 mixins: [ BVPaginationMixin ], 650 data() { 651 return { 652 items: [...], 653 fields: [..], 654 currentPage, 655 perPage, 656 itemsPerPageOptions 657 }, 658 } 659} 660</script> 661``` 662