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