blob: a3824295b47cc9f792675ed83e95b8f13bd6518a [file] [log] [blame]
Yoshie Muranakafa562732019-07-17 11:23:15 -05001window.angular && (function(angular) {
2 'use strict';
3
4 /**
5 *
Yoshie Muranakabb688792019-08-12 09:31:52 -05006 * bmcTable Component
Yoshie Muranakafa562732019-07-17 11:23:15 -05007 *
8 * To use:
Yoshie Muranakafa562732019-07-17 11:23:15 -05009 *
Yoshie Muranakab1f64242019-09-04 11:40:51 -070010 * 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'
Yoshie Muranakafa562732019-07-17 11:23:15 -050013 * property that should be an array of the properties that will render
14 * as each table cell <td>.
Yoshie Muranakab1f64242019-09-04 11:40:51 -070015 * Each row object in the data array can optionally have an
Yoshie Muranakabb688792019-08-12 09:31:52 -050016 * 'actions' property that should be an array of actions to provide the
17 * <bmc-table-actions> component.
Yoshie Muranaka1d83af02019-09-06 08:52:35 -070018 * 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.
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -070021 * 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.
Yoshie Muranakafa562732019-07-17 11:23:15 -050024 *
Yoshie Muranakab1f64242019-09-04 11:40:51 -070025 * data = [
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -070026 * { uiData: ['root', 'Admin', 'enabled' ], selectable: false },
Yoshie Muranakab1f64242019-09-04 11:40:51 -070027 * { 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.
Yoshie Muranaka1d83af02019-09-06 08:52:35 -070048 * 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
Yoshie Muranakab1f64242019-09-04 11:40:51 -070052 *
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -070053 * 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 *
Yoshie Muranakab1f64242019-09-04 11:40:51 -070057 * The 'size' attribute which can be set to 'small' which will
58 * render a smaller font size in the table.
Yoshie Muranakafa562732019-07-17 11:23:15 -050059 *
60 */
Yoshie Muranakabb688792019-08-12 09:31:52 -050061
Yoshie Muranakafa562732019-07-17 11:23:15 -050062 const TableController = function() {
Yoshie Muranakab1f64242019-09-04 11:40:51 -070063 this.sortAscending = true;
64 this.activeSort;
Yoshie Muranaka1d83af02019-09-06 08:52:35 -070065 this.expandedRows = new Set();
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -070066 this.selectedRows = new Set();
67 this.selectHeaderCheckbox = false;
68 this.someSelected = false;
69
70 let selectableRowCount = 0;
Yoshie Muranakab1f64242019-09-04 11:40:51 -070071
Yoshie Muranakafa562732019-07-17 11:23:15 -050072 /**
Yoshie Muranakab1f64242019-09-04 11:40:51 -070073 * Sorts table data
Yoshie Muranakafa562732019-07-17 11:23:15 -050074 */
Yoshie Muranakab1f64242019-09-04 11:40:51 -070075 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;
Yoshie Muranakafa562732019-07-17 11:23:15 -050086 }
Yoshie Muranakafa562732019-07-17 11:23:15 -050087 })
Yoshie Muranakafa562732019-07-17 11:23:15 -050088 };
89
90 /**
Yoshie Muranaka1d83af02019-09-06 08:52:35 -070091 * 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 }
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -0700104 };
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 }
Yoshie Muranaka1d83af02019-09-06 08:52:35 -0700135 }
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -0700136 };
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 })
Yoshie Muranaka1d83af02019-09-06 08:52:35 -0700168 };
169
170 /**
Yoshie Muranakafa562732019-07-17 11:23:15 -0500171 * 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 */
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -0700177 this.onEmitRowAction = (action, row) => {
Yoshie Muranakafa562732019-07-17 11:23:15 -0500178 if (action !== undefined && row !== undefined) {
179 const value = {action, row};
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -0700180 this.emitRowAction({value});
Yoshie Muranakafa562732019-07-17 11:23:15 -0500181 }
182 };
183
184 /**
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -0700185 * 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 /**
Yoshie Muranakab1f64242019-09-04 11:40:51 -0700197 * 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 /**
Yoshie Muranaka1d83af02019-09-06 08:52:35 -0700214 * 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 /**
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -0700226 * 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 /**
Yoshie Muranakab1f64242019-09-04 11:40:51 -0700269 * onInit Component lifecycle hook
270 * Checking for undefined values
Yoshie Muranakafa562732019-07-17 11:23:15 -0500271 */
272 this.$onInit = () => {
Yoshie Muranakab1f64242019-09-04 11:40:51 -0700273 this.header = this.header === undefined ? [] : this.header;
274 this.data = this.data == undefined ? [] : this.data;
275 this.sortable = this.sortable === undefined ? false : this.sortable;
Yoshie Muranakabb688792019-08-12 09:31:52 -0500276 this.rowActionsEnabled =
Yoshie Muranakab1f64242019-09-04 11:40:51 -0700277 this.rowActionsEnabled === undefined ? false : this.rowActionsEnabled;
278 this.size = this.size === undefined ? '' : this.size;
Yoshie Muranaka1d83af02019-09-06 08:52:35 -0700279 this.expandable = this.expandable === undefined ? false : this.expandable;
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -0700280 this.selectable = this.selectable === undefined ? false : this.selectable;
Yoshie Muranaka1d83af02019-09-06 08:52:35 -0700281 prepTable();
Yoshie Muranakafa562732019-07-17 11:23:15 -0500282 };
Yoshie Muranakab1f64242019-09-04 11:40:51 -0700283
284 /**
285 * onChanges Component lifecycle hook
Yoshie Muranakab1f64242019-09-04 11:40:51 -0700286 */
287 this.$onChanges = (onChangesObj) => {
288 const dataChange = onChangesObj.data;
289 if (dataChange) {
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -0700290 prepData();
Yoshie Muranaka49001e22019-09-16 10:33:16 -0700291 deselectAllRows();
Yoshie Muranakab1f64242019-09-04 11:40:51 -0700292 }
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -0700293 };
Yoshie Muranakafa562732019-07-17 11:23:15 -0500294 };
295
296 /**
297 * Register bmcTable component
298 */
299 angular.module('app.common.components').component('bmcTable', {
300 template: require('./table.html'),
301 controller: TableController,
Yoshie Muranakab1f64242019-09-04 11:40:51 -0700302 bindings: {
303 data: '<', // Array
304 header: '<', // Array
305 rowActionsEnabled: '<', // boolean
306 size: '<', // string
307 sortable: '<', // boolean
308 defaultSort: '<', // number (index of sort)
Yoshie Muranaka1d83af02019-09-06 08:52:35 -0700309 expandable: '<', // boolean
Yoshie Muranaka5b8cef82019-09-10 08:09:43 -0700310 selectable: '<', // boolean
311 batchActions: '<', // Array
312 emitRowAction: '&',
313 emitBatchAction: '&'
Yoshie Muranakab1f64242019-09-04 11:40:51 -0700314 }
Yoshie Muranakafa562732019-07-17 11:23:15 -0500315 })
316})(window.angular);