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