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