xref: /openbmc/phosphor-webui/app/common/components/table/table.js (revision 1d83af072eb4c047ef08fae79775a34599ed5ed3)
1window.angular && (function(angular) {
2  'use strict';
3
4  /**
5   *
6   * bmcTable Component
7   *
8   * To use:
9   *
10   * The 'data' attribute should be an array of all row objects in the table.
11   * It will render each item as a <tr> in the table.
12   * Each row object in the data array should also have a 'uiData'
13   * property that should be an array of the properties that will render
14   * as each table cell <td>.
15   * Each row object in the data array can optionally have an
16   * 'actions' property that should be an array of actions to provide the
17   * <bmc-table-actions> component.
18   * Each row object can optionally have an 'expandContent' property
19   * that should be a string value and can contain valid HTML. To render
20   * the expanded content, set 'expandable' attribute to true.
21   *
22   * data = [
23   *  { uiData: ['root', 'Admin', 'enabled' ] },
24   *  { uiData: ['user1', 'User', 'disabled' ] }
25   * ]
26   *
27   * The 'header' attribute should be an array of all header objects in the
28   * table. Each object in the header array should have a 'label' property
29   * that will render as a <th> in the table.
30   * If the table is sortable, can optionally add 'sortable' property to header
31   * row object. If a particular column is not sortable, set to false.
32   *
33   * header = [
34   *  { label: 'Username' },
35   *  { label: 'Privilege' }
36   *  { label: 'Account Status', sortable: false }
37   * ]
38   *
39   * The 'sortable' attribute should be a boolean value. Defaults to false.
40   * The 'default-sort' attribute should be the index value of the header
41   * obejct that should be sorted on inital load.
42   *
43   * The 'row-actions-enabled' attribute, should be a boolean value
44   * Can be set to true to render table row actions. Defaults to false.
45   * Row actions are defined in data.actions.
46   *
47   * The 'expandable' attribute should be a boolean value. If true each
48   * row object in data array should contain a 'expandContent' property
49   *
50   * The 'size' attribute which can be set to 'small' which will
51   * render a smaller font size in the table.
52   *
53   */
54
55  const TableController = function() {
56    this.sortAscending = true;
57    this.activeSort;
58    this.expandedRows = new Set();
59
60    /**
61     * Sorts table data
62     */
63    const sortData = () => {
64      this.data.sort((a, b) => {
65        const aProp = a.uiData[this.activeSort];
66        const bProp = b.uiData[this.activeSort];
67        if (aProp === bProp) {
68          return 0;
69        } else {
70          if (this.sortAscending) {
71            return aProp < bProp ? -1 : 1;
72          }
73          return aProp > bProp ? -1 : 1;
74        }
75      })
76    };
77
78    /**
79     * Prep table
80     * Make adjustments to account for optional configurations
81     */
82    const prepTable = () => {
83      if (this.sortable) {
84        // If sort is enabled, check for undefined 'sortable'
85        // property for each item in header array
86        this.header = this.header.map((column) => {
87          column.sortable =
88              column.sortable === undefined ? true : column.sortable;
89          return column;
90        })
91      }
92      if (this.rowActionsEnabled) {
93        // If table actions are enabled push an empty
94        // string to the header array to account for additional
95        // table actions cell
96        this.header.push({label: '', sortable: false});
97      }
98      if (this.expandable) {
99        // If table is expandable, push an empty string to the
100        // header array to account for additional expansion cell
101        this.header.unshift({label: '', sortable: false});
102      }
103    };
104
105    /**
106     * Callback when table row action clicked
107     * Emits user desired action and associated row data to
108     * parent controller
109     * @param {string} action : action type
110     * @param {any} row : user object
111     */
112    this.onEmitTableAction = (action, row) => {
113      if (action !== undefined && row !== undefined) {
114        const value = {action, row};
115        this.emitAction({value});
116      }
117    };
118
119    /**
120     * Callback when sortable table header clicked
121     * @param {number} index : index of header item
122     */
123    this.onClickSort = (index) => {
124      if (index === this.activeSort) {
125        // If clicked header is already sorted, reverse
126        // the sort direction
127        this.sortAscending = !this.sortAscending;
128        this.data.reverse();
129      } else {
130        this.sortAscending = true;
131        this.activeSort = index;
132        sortData();
133      }
134    };
135
136    /**
137     * Callback when expand trigger clicked
138     * @param {number} row : index of expanded row
139     */
140    this.onClickExpand = (row) => {
141      if (this.expandedRows.has(row)) {
142        this.expandedRows.delete(row)
143      } else {
144        this.expandedRows.add(row);
145      }
146    };
147
148    /**
149     * onInit Component lifecycle hook
150     * Checking for undefined values
151     */
152    this.$onInit = () => {
153      this.header = this.header === undefined ? [] : this.header;
154      this.data = this.data == undefined ? [] : this.data;
155      this.sortable = this.sortable === undefined ? false : this.sortable;
156      this.rowActionsEnabled =
157          this.rowActionsEnabled === undefined ? false : this.rowActionsEnabled;
158      this.size = this.size === undefined ? '' : this.size;
159      this.expandable = this.expandable === undefined ? false : this.expandable;
160
161      // Check for undefined 'uiData' property for each item in data array
162      this.data = this.data.map((row) => {
163        if (row.uiData === undefined) {
164          row.uiData = [];
165        }
166        return row;
167      })
168      prepTable();
169    };
170
171    /**
172     * onChanges Component lifecycle hook
173     * Check for changes in the data array and apply
174     * default or active sort if one is defined
175     */
176    this.$onChanges = (onChangesObj) => {
177      const dataChange = onChangesObj.data;
178      if (dataChange) {
179        if (this.activeSort !== undefined || this.defaultSort !== undefined) {
180          this.activeSort = this.defaultSort !== undefined ? this.defaultSort :
181                                                             this.activeSort;
182          sortData();
183        }
184      }
185    }
186  };
187
188  /**
189   * Register bmcTable component
190   */
191  angular.module('app.common.components').component('bmcTable', {
192    template: require('./table.html'),
193    controller: TableController,
194    bindings: {
195      data: '<',               // Array
196      header: '<',             // Array
197      rowActionsEnabled: '<',  // boolean
198      size: '<',               // string
199      sortable: '<',           // boolean
200      defaultSort: '<',        // number (index of sort)
201      expandable: '<',         // boolean
202      emitAction: '&'
203    }
204  })
205})(window.angular);
206