window.angular && (function(angular) { 'use strict'; /** * * bmcTable Component * * To use: * * The 'data' attribute should be an array of all row objects in the table. * It will render each item as a in the table. * Each row object in the data array should also have a 'uiData' * property that should be an array of the properties that will render * as each table cell . * Each row object in the data array can optionally have an * 'actions' property that should be an array of actions to provide the * component. * Each row object can optionally have an 'expandContent' property * that should be a string value and can contain valid HTML. To render * the expanded content, set 'expandable' attribute to true. * Each row object can optionally have a 'selectable' property. Defaults * to true if table is selectable. If a particular row should not * be selectable, set to false. * * data = [ * { uiData: ['root', 'Admin', 'enabled' ], selectable: false }, * { uiData: ['user1', 'User', 'disabled' ] } * ] * * The 'header' attribute should be an array of all header objects in the * table. Each object in the header array should have a 'label' property * that will render as a in the table. * If the table is sortable, can optionally add 'sortable' property to header * row object. If a particular column is not sortable, set to false. * * header = [ * { label: 'Username' }, * { label: 'Privilege' } * { label: 'Account Status', sortable: false } * ] * * The 'sortable' attribute should be a boolean value. Defaults to false. * The 'default-sort' attribute should be the index value of the header * obejct that should be sorted on inital load. * * The 'row-actions-enabled' attribute, should be a boolean value * Can be set to true to render table row actions. Defaults to false. * Row actions are defined in data.actions. * * The 'expandable' attribute should be a boolean value. If true each * row object in data array should contain a 'expandContent' property * * The 'selectable' attribute should be a boolean value. * If 'selectable' is true, include 'batch-actions' property that should * be an array of actions to provide component. * * The 'size' attribute which can be set to 'small' which will * render a smaller font size in the table. * */ const TableController = function() { this.sortAscending = true; this.activeSort; this.expandedRows = new Set(); this.selectedRows = new Set(); this.selectHeaderCheckbox = false; this.someSelected = false; let selectableRowCount = 0; /** * Sorts table data */ const sortData = () => { this.data.sort((a, b) => { const aProp = a.uiData[this.activeSort]; const bProp = b.uiData[this.activeSort]; if (aProp === bProp) { return 0; } else { if (this.sortAscending) { return aProp < bProp ? -1 : 1; } return aProp > bProp ? -1 : 1; } }) }; /** * Prep table * Make adjustments to account for optional configurations */ const prepTable = () => { if (this.sortable) { // If sort is enabled, check for undefined 'sortable' // property for each item in header array this.header = this.header.map((column) => { column.sortable = column.sortable === undefined ? true : column.sortable; return column; }) } }; /** * Prep data * When data binding changes, make adjustments to account for * optional configurations and undefined values */ const prepData = () => { selectableRowCount = 0; this.data.forEach((row) => { if (row.uiData === undefined) { // Check for undefined 'uiData' property for each item in data // array row.uiData = []; } if (this.selectable) { // If table is selectable check row for 'selectable' property row.selectable = row.selectable === undefined ? true : row.selectable; if (row.selectable) { selectableRowCount++; row.selected = false; } } }); if (this.sortable) { if (this.activeSort !== undefined || this.defaultSort !== undefined) { // apply default or active sort if one is defined this.activeSort = this.defaultSort !== undefined ? this.defaultSort : this.activeSort; sortData(); } } }; /** * Select all rows * Sets each selectable row selected property to true * and adds index to selectedRow Set */ const selectAllRows = () => { this.selectHeaderCheckbox = true; this.someSelected = false; this.data.forEach((row, index) => { if (!row.selected && row.selectable) { row.selected = true; this.selectedRows.add(index); } }) }; /** * Deselect all rows * Sets each row selected property to false * and clears selectedRow Set */ const deselectAllRows = () => { this.selectHeaderCheckbox = false; this.someSelected = false; this.selectedRows.clear(); this.data.forEach((row) => { if (row.selectable) { row.selected = false; } }) }; /** * Callback when table row action clicked * Emits user desired action and associated row data to * parent controller * @param {string} action : action type * @param {any} row : user object */ this.onEmitRowAction = (action, row) => { if (action !== undefined && row !== undefined) { const value = {action, row}; this.emitRowAction({value}); } }; /** * Callback when batch action clicked from toolbar * Emits the action type and the selected row data to * parent controller * @param {string} action : action type */ this.onEmitBatchAction = (action) => { const filteredRows = this.data.filter((row) => row.selected); const value = {action, filteredRows}; this.emitBatchAction({value}); }; /** * Callback when sortable table header clicked * @param {number} index : index of header item */ this.onClickSort = (index) => { if (index === this.activeSort) { // If clicked header is already sorted, reverse // the sort direction this.sortAscending = !this.sortAscending; this.data.reverse(); } else { this.sortAscending = true; this.activeSort = index; sortData(); } }; /** * Callback when expand trigger clicked * @param {number} row : index of expanded row */ this.onClickExpand = (row) => { if (this.expandedRows.has(row)) { this.expandedRows.delete(row) } else { this.expandedRows.add(row); } }; /** * Callback when select checkbox clicked * @param {number} row : index of selected row */ this.onRowSelectChange = (row) => { if (this.selectedRows.has(row)) { this.selectedRows.delete(row); } else { this.selectedRows.add(row); } if (this.selectedRows.size === 0) { this.someSelected = false; this.selectHeaderCheckbox = false; deselectAllRows(); } else if (this.selectedRows.size === selectableRowCount) { this.someSelected = false; this.selectHeaderCheckbox = true; selectAllRows(); } else { this.someSelected = true; } }; /** * Callback when header select box value changes */ this.onHeaderSelectChange = (checked) => { this.selectHeaderCheckbox = checked; if (this.selectHeaderCheckbox) { selectAllRows(); } else { deselectAllRows(); } }; /** * Callback when cancel/close button closed * from toolbar */ this.onToolbarClose = () => { deselectAllRows(); }; /** * onInit Component lifecycle hook * Checking for undefined values */ this.$onInit = () => { this.header = this.header === undefined ? [] : this.header; this.data = this.data == undefined ? [] : this.data; this.sortable = this.sortable === undefined ? false : this.sortable; this.rowActionsEnabled = this.rowActionsEnabled === undefined ? false : this.rowActionsEnabled; this.size = this.size === undefined ? '' : this.size; this.expandable = this.expandable === undefined ? false : this.expandable; this.selectable = this.selectable === undefined ? false : this.selectable; prepTable(); }; /** * onChanges Component lifecycle hook */ this.$onChanges = (onChangesObj) => { const dataChange = onChangesObj.data; if (dataChange) { prepData(); deselectAllRows(); } }; }; /** * Register bmcTable component */ angular.module('app.common.components').component('bmcTable', { template: require('./table.html'), controller: TableController, bindings: { data: '<', // Array header: '<', // Array rowActionsEnabled: '<', // boolean size: '<', // string sortable: '<', // boolean defaultSort: '<', // number (index of sort) expandable: '<', // boolean selectable: '<', // boolean batchActions: '<', // Array emitRowAction: '&', emitBatchAction: '&' } }) })(window.angular);