Add tableActions component
Creating a separate table actions component to allow
row action customizations like disabling certain
actions and rendering different icons.
Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I85e96045af27701f5ecc4af9bf824e248abccbf5
diff --git a/app/common/components/table/table-actions.js b/app/common/components/table/table-actions.js
new file mode 100644
index 0000000..e1e47ec
--- /dev/null
+++ b/app/common/components/table/table-actions.js
@@ -0,0 +1,91 @@
+window.angular && (function(angular) {
+ 'use strict';
+
+ /**
+ *
+ * tableActions Component
+ *
+ * To use:
+ * The <table-actions> component expects an 'actions' attribute
+ * that should be an array of action objects.
+ * Each action object should have 'type', 'enabled', and 'file'
+ * properties.
+ *
+ * actions: [
+ * {type: 'Edit', enabled: true, file: 'icon-edit.svg'},
+ * {type: 'Delete', enabled: false, file: 'icon-trashcan.svg'}
+ * ]
+ *
+ * The 'type' property is a string value that will be emitted to the
+ * parent component when clicked.
+ *
+ * The 'enabled' property is a boolean that will enable/disable
+ * the button.
+ *
+ * The 'file' property is a string value of the filename of the svg icon
+ * to provide <icon> directive.
+ *
+ */
+
+ const controller = function() {
+ /**
+ * Set defaults if properties undefined
+ * @param {[]} actions
+ */
+ const setActions = (actions = []) => {
+ return actions
+ .map((action) => {
+ if (action.type === undefined) {
+ return;
+ }
+ if (action.file === undefined) {
+ action.file = null;
+ }
+ if (action.enabled === undefined) {
+ action.enabled = true;
+ }
+ return action;
+ })
+ .filter((action) => action);
+ };
+
+ /**
+ * Callback when button action clicked
+ * Emits the action type to the parent component
+ */
+ this.onClick = (action) => {
+ this.emitAction({action});
+ };
+
+ /**
+ * onInit Component lifecycle hook
+ */
+ this.$onInit = () => {
+ this.actions = setActions(this.actions);
+ };
+ };
+
+ /**
+ * Component template
+ */
+ const template = `
+ <button
+ class="btn btn-tertiary"
+ type="button"
+ aria-label="{{action.type}}"
+ ng-repeat="action in $ctrl.actions"
+ ng-disabled="!action.enabled"
+ ng-click="$ctrl.onClick(action.type)">
+ <icon ng-if="action.file !== null" ng-file="{{action.file}}"></icon>
+ <span ng-if="action.file === null">{{action.type}}</span>
+ </button>`
+
+ /**
+ * Register tableActions component
+ */
+ angular.module('app.common.components').component('tableActions', {
+ controller,
+ template,
+ bindings: {actions: '<', emitAction: '&'}
+ })
+})(window.angular);
\ No newline at end of file
diff --git a/app/common/components/table/table.html b/app/common/components/table/table.html
index 6ec520c..b40c346 100644
--- a/app/common/components/table/table.html
+++ b/app/common/components/table/table.html
@@ -19,13 +19,12 @@
{{item}}
</td>
<!-- Row Actions -->
- <td ng-if="$ctrl.model.actions.length > 0"
+ <td ng-if="$ctrl.rowActionsEnabled"
class="bmc-table__cell bmc-table__row-actions">
- <button ng-repeat="action in $ctrl.model.actions"
- ng-click="$ctrl.onClickAction(action, row);"
- class="btn btn-tertiary">
- {{action}}
- </button>
+ <table-actions
+ actions="row.actions"
+ emit-action="$ctrl.onEmitTableAction(action, row)">
+ </table-actions>
</td>
</tr>
<!-- Empty table -->
diff --git a/app/common/components/table/table.js b/app/common/components/table/table.js
index 2d7fc77..09a6d6d 100644
--- a/app/common/components/table/table.js
+++ b/app/common/components/table/table.js
@@ -3,19 +3,24 @@
/**
*
- * Controller for bmcTable Component
+ * bmcTable Component
*
* To use:
* The <bmc-table> component expects a 'model' attribute
* that will contain all the data needed to render the table.
*
- * The model object should contain 'header', 'data', and 'actions'
+ * The component also accepts a 'row-actions-enabled' attribute,
+ * to optionally render table row actions. Defaults to false.
+ * Pass true to render actions. Row actions are defined in
+ * model.data.actions.
+ *
+ *
+ * The model object should contain 'header', and 'data'
* properties.
*
* model: {
- * header: <string>[], // Array of header labels
- * data: <any>[], // Array of each row object
- * actions: <string>[] // Array of action labels
+ * header: <string>[], // Array of header labels
+ * data: <any>[], // Array of each row object
* }
*
* The header property will render each label as a <th> in the table.
@@ -24,13 +29,15 @@
* Each row object in the model.data array should also have a 'uiData'
* property that should be an array of the properties that will render
* as each table cell <td>.
+ * Each row object in the model.data array can optionally have an
+ * 'actions' property that should be an array of actions to provide the
+ * <bmc-table-actions> component.
*
- * The actions property will render into clickable buttons at the end
- * of each row.
- * When a user clicks an action button, the component
- * will emit the action label with the associated row object.
+ * The 'rowActionsEnabled' property will render <bmc-table-actions> if set
+ * to true.
*
*/
+
const TableController = function() {
/**
* Init model data
@@ -46,14 +53,6 @@
}
return row;
})
- model.actions = model.actions === undefined ? [] : model.actions;
-
- if (model.actions.length > 0) {
- // If table actions were provided, push an empty
- // string to the header array to account for additional
- // table actions cell
- model.header.push('');
- }
return model;
};
@@ -64,7 +63,7 @@
* @param {string} action : action type
* @param {any} row : user object
*/
- this.onClickAction = (action, row) => {
+ this.onEmitTableAction = (action, row) => {
if (action !== undefined && row !== undefined) {
const value = {action, row};
this.emitAction({value});
@@ -75,7 +74,19 @@
* onInit Component lifecycle hooked
*/
this.$onInit = () => {
+ if (this.model === undefined) {
+ console.log('<bmc-table> Component is missing "model" attribute.');
+ return;
+ }
this.model = setModel(this.model);
+ this.rowActionsEnabled =
+ this.rowActionsEnabled === undefined ? false : true;
+ if (this.rowActionsEnabled) {
+ // If table actions are enabled push an empty
+ // string to the header array to account for additional
+ // table actions cell
+ this.model.header.push('');
+ }
};
};
@@ -85,6 +96,6 @@
angular.module('app.common.components').component('bmcTable', {
template: require('./table.html'),
controller: TableController,
- bindings: {model: '<', emitAction: '&'}
+ bindings: {model: '<', rowActionsEnabled: '<', emitAction: '&'}
})
})(window.angular);
diff --git a/app/common/directives/icon-provider.js b/app/common/directives/icon-provider.js
index 5554fdd..bee6150 100644
--- a/app/common/directives/icon-provider.js
+++ b/app/common/directives/icon-provider.js
@@ -13,8 +13,9 @@
angular.module('app.common.directives').directive('icon', () => {
return {
- restrict: 'E', link: (scope, element, attrs) => {
- const file = attrs.file;
+ restrict: 'E',
+ link: (scope, element, attrs) => {
+ const file = attrs.file || attrs.ngFile;
if (file === undefined) {
console.log('File name not provided for <icon> directive.')
return;
@@ -23,6 +24,6 @@
element.html(svg);
element.addClass('icon');
}
- }
+ };
})
})(window.angular);
\ No newline at end of file
diff --git a/app/common/styles/components/table.scss b/app/common/styles/components/table.scss
index 17df264..40b6a64 100644
--- a/app/common/styles/components/table.scss
+++ b/app/common/styles/components/table.scss
@@ -161,10 +161,14 @@
}
.bmc-table__cell {
- padding: 4px 16px;
+ padding: 10px 16px;
background-color: $base-02--07;
}
.bmc-table__row-actions {
text-align: right;
+ .btn {
+ padding-top: 0;
+ padding-bottom: 0;
+ }
}
\ No newline at end of file
diff --git a/app/index.js b/app/index.js
index 32acb66..c839708 100644
--- a/app/index.js
+++ b/app/index.js
@@ -67,6 +67,7 @@
import components_index from './common/components/index.js';
import table_component from './common/components/table/table.js';
+import table_actions_component from './common/components/table/table-actions.js';
import login_index from './login/index.js';
import login_controller from './login/controllers/login-controller.js';
diff --git a/app/users/controllers/user-accounts-controller.html b/app/users/controllers/user-accounts-controller.html
index fd6a28c..41094b8 100644
--- a/app/users/controllers/user-accounts-controller.html
+++ b/app/users/controllers/user-accounts-controller.html
@@ -23,6 +23,7 @@
</div>
<bmc-table
model="tableModel"
+ row-actions-enabled="true"
emit-action="onEmitAction(value)"
class="local-users__table">
</bmc-table>
diff --git a/app/users/controllers/user-accounts-controller.js b/app/users/controllers/user-accounts-controller.js
index 3d63d69..b5ed1a5 100644
--- a/app/users/controllers/user-accounts-controller.js
+++ b/app/users/controllers/user-accounts-controller.js
@@ -19,16 +19,24 @@
$scope.tableModel = {};
$scope.tableModel.data = [];
- $scope.tableModel.header = ['Username', 'Privilege', 'Account status']
- $scope.tableModel.actions = ['Edit', 'Delete'];
+ $scope.tableModel.header = ['Username', 'Privilege', 'Account status'];
/**
* Data table mapper
* @param {*} user
+ * @returns user
*/
function mapTableData(user) {
- let accountStatus =
+ const accountStatus =
user.Locked ? 'Locked' : user.Enabled ? 'Enabled' : 'Disabled';
+ const editAction = {type: 'Edit', enabled: true, file: 'icon-edit.svg'};
+ const deleteAction = {
+ type: 'Delete',
+ enabled: user.UserName === 'root' ? false : true,
+ file: 'icon-trashcan.svg'
+ };
+
+ user.actions = [editAction, deleteAction];
user.uiData = [user.UserName, user.RoleId, accountStatus];
return user;
}