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);