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   * Each row object can optionally have a 'selectable' property. Defaults
22   * to true if table is selectable. If a particular row should not
23   * be selectable, set to false.
24   *
25   * data = [
26   *  { uiData: ['root', 'Admin', 'enabled' ], selectable: false },
27   *  { uiData: ['user1', 'User', 'disabled' ] }
28   * ]
29   *
30   * The 'header' attribute should be an array of all header objects in the
31   * table. Each object in the header array should have a 'label' property
32   * that will render as a <th> in the table.
33   * If the table is sortable, can optionally add 'sortable' property to header
34   * row object. If a particular column is not sortable, set to false.
35   *
36   * header = [
37   *  { label: 'Username' },
38   *  { label: 'Privilege' }
39   *  { label: 'Account Status', sortable: false }
40   * ]
41   *
42   * The 'sortable' attribute should be a boolean value. Defaults to false.
43   * The 'default-sort' attribute should be the index value of the header
44   * obejct that should be sorted on inital load.
45   *
46   * The 'row-actions-enabled' attribute, should be a boolean value
47   * Can be set to true to render table row actions. Defaults to false.
48   * Row actions are defined in data.actions.
49   *
50   * The 'expandable' attribute should be a boolean value. If true each
51   * row object in data array should contain a 'expandContent' property
52   *
53   * The 'selectable' attribute should be a boolean value.
54   * If 'selectable' is true, include 'batch-actions' property that should
55   * be an array of actions to provide <table-toolbar> component.
56   *
57   * The 'size' attribute which can be set to 'small' which will
58   * render a smaller font size in the table.
59   *
60   */
61
62  const TableController = function() {
63    this.sortAscending = true;
64    this.activeSort;
65    this.expandedRows = new Set();
66    this.selectedRows = new Set();
67    this.selectHeaderCheckbox = false;
68    this.someSelected = false;
69
70    let selectableRowCount = 0;
71
72    /**
73     * Sorts table data
74     */
75    const sortData = () => {
76      this.data.sort((a, b) => {
77        const aProp = a.uiData[this.activeSort];
78        const bProp = b.uiData[this.activeSort];
79        if (aProp === bProp) {
80          return 0;
81        } else {
82          if (this.sortAscending) {
83            return aProp < bProp ? -1 : 1;
84          }
85          return aProp > bProp ? -1 : 1;
86        }
87      })
88    };
89
90    /**
91     * Prep table
92     * Make adjustments to account for optional configurations
93     */
94    const prepTable = () => {
95      if (this.sortable) {
96        // If sort is enabled, check for undefined 'sortable'
97        // property for each item in header array
98        this.header = this.header.map((column) => {
99          column.sortable =
100              column.sortable === undefined ? true : column.sortable;
101          return column;
102        })
103      }
104    };
105
106    /**
107     * Prep data
108     * When data binding changes, make adjustments to account for
109     * optional configurations and undefined values
110     */
111    const prepData = () => {
112      selectableRowCount = 0;
113      this.data.forEach((row) => {
114        if (row.uiData === undefined) {
115          // Check for undefined 'uiData' property for each item in data
116          // array
117          row.uiData = [];
118        }
119        if (this.selectable) {
120          // If table is selectable check row for 'selectable' property
121          row.selectable = row.selectable === undefined ? true : row.selectable;
122          if (row.selectable) {
123            selectableRowCount++;
124            row.selected = false;
125          }
126        }
127      });
128      if (this.sortable) {
129        if (this.activeSort !== undefined || this.defaultSort !== undefined) {
130          // apply default or active sort if one is defined
131          this.activeSort = this.defaultSort !== undefined ? this.defaultSort :
132                                                             this.activeSort;
133          sortData();
134        }
135      }
136    };
137
138    /**
139     * Select all rows
140     * Sets each selectable row selected property to true
141     * and adds index to selectedRow Set
142     */
143    const selectAllRows = () => {
144      this.selectHeaderCheckbox = true;
145      this.someSelected = false;
146      this.data.forEach((row, index) => {
147        if (!row.selected && row.selectable) {
148          row.selected = true;
149          this.selectedRows.add(index);
150        }
151      })
152    };
153
154    /**
155     * Deselect all rows
156     * Sets each row selected property to false
157     * and clears selectedRow Set
158     */
159    const deselectAllRows = () => {
160      this.selectHeaderCheckbox = false;
161      this.someSelected = false;
162      this.selectedRows.clear();
163      this.data.forEach((row) => {
164        if (row.selectable) {
165          row.selected = false;
166        }
167      })
168    };
169
170    /**
171     * Callback when table row action clicked
172     * Emits user desired action and associated row data to
173     * parent controller
174     * @param {string} action : action type
175     * @param {any} row : user object
176     */
177    this.onEmitRowAction = (action, row) => {
178      if (action !== undefined && row !== undefined) {
179        const value = {action, row};
180        this.emitRowAction({value});
181      }
182    };
183
184    /**
185     * Callback when batch action clicked from toolbar
186     * Emits the action type and the selected row data to
187     * parent controller
188     * @param {string} action : action type
189     */
190    this.onEmitBatchAction = (action) => {
191      const filteredRows = this.data.filter((row) => row.selected);
192      const value = {action, filteredRows};
193      this.emitBatchAction({value});
194    };
195
196    /**
197     * Callback when sortable table header clicked
198     * @param {number} index : index of header item
199     */
200    this.onClickSort = (index) => {
201      if (index === this.activeSort) {
202        // If clicked header is already sorted, reverse
203        // the sort direction
204        this.sortAscending = !this.sortAscending;
205        this.data.reverse();
206      } else {
207        this.sortAscending = true;
208        this.activeSort = index;
209        sortData();
210      }
211    };
212
213    /**
214     * Callback when expand trigger clicked
215     * @param {number} row : index of expanded row
216     */
217    this.onClickExpand = (row) => {
218      if (this.expandedRows.has(row)) {
219        this.expandedRows.delete(row)
220      } else {
221        this.expandedRows.add(row);
222      }
223    };
224
225    /**
226     * Callback when select checkbox clicked
227     * @param {number} row : index of selected row
228     */
229    this.onRowSelectChange = (row) => {
230      if (this.selectedRows.has(row)) {
231        this.selectedRows.delete(row);
232      } else {
233        this.selectedRows.add(row);
234      }
235      if (this.selectedRows.size === 0) {
236        this.someSelected = false;
237        this.selectHeaderCheckbox = false;
238        deselectAllRows();
239      } else if (this.selectedRows.size === selectableRowCount) {
240        this.someSelected = false;
241        this.selectHeaderCheckbox = true;
242        selectAllRows();
243      } else {
244        this.someSelected = true;
245      }
246    };
247
248    /**
249     * Callback when header select box value changes
250     */
251    this.onHeaderSelectChange = (checked) => {
252      this.selectHeaderCheckbox = checked;
253      if (this.selectHeaderCheckbox) {
254        selectAllRows();
255      } else {
256        deselectAllRows();
257      }
258    };
259
260    /**
261     * Callback when cancel/close button closed
262     * from toolbar
263     */
264    this.onToolbarClose = () => {
265      deselectAllRows();
266    };
267
268    /**
269     * onInit Component lifecycle hook
270     * Checking for undefined values
271     */
272    this.$onInit = () => {
273      this.header = this.header === undefined ? [] : this.header;
274      this.data = this.data == undefined ? [] : this.data;
275      this.sortable = this.sortable === undefined ? false : this.sortable;
276      this.rowActionsEnabled =
277          this.rowActionsEnabled === undefined ? false : this.rowActionsEnabled;
278      this.size = this.size === undefined ? '' : this.size;
279      this.expandable = this.expandable === undefined ? false : this.expandable;
280      this.selectable = this.selectable === undefined ? false : this.selectable;
281      prepTable();
282    };
283
284    /**
285     * onChanges Component lifecycle hook
286     */
287    this.$onChanges = (onChangesObj) => {
288      const dataChange = onChangesObj.data;
289      if (dataChange) {
290        prepData();
291        deselectAllRows();
292      }
293    };
294  };
295
296  /**
297   * Register bmcTable component
298   */
299  angular.module('app.common.components').component('bmcTable', {
300    template: require('./table.html'),
301    controller: TableController,
302    bindings: {
303      data: '<',               // Array
304      header: '<',             // Array
305      rowActionsEnabled: '<',  // boolean
306      size: '<',               // string
307      sortable: '<',           // boolean
308      defaultSort: '<',        // number (index of sort)
309      expandable: '<',         // boolean
310      selectable: '<',         // boolean
311      batchActions: '<',       // Array
312      emitRowAction: '&',
313      emitBatchAction: '&'
314    }
315  })
316})(window.angular);
317