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