Add expand/collapse functionality to table Component

This commit will add optional expand/collapse functionality
to the shared table component. Expand/collapse is not
implemented on any existing table but will be used on the
redesigned event log table.

Tested on Chrome, Safari, Firefox, Edge, IE

Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: Ia7ecde7b5525c11c68ebdf9f609c8d690c312969
diff --git a/app/common/components/table/table.js b/app/common/components/table/table.js
index 24aa0c9..5db05b6 100644
--- a/app/common/components/table/table.js
+++ b/app/common/components/table/table.js
@@ -15,6 +15,9 @@
    * Each row object in the data array can optionally have an
    * 'actions' property that should be an array of actions to provide the
    * <bmc-table-actions> 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.
    *
    * data = [
    *  { uiData: ['root', 'Admin', 'enabled' ] },
@@ -39,7 +42,10 @@
    *
    * 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.
+   * 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 'size' attribute which can be set to 'small' which will
    * render a smaller font size in the table.
@@ -49,6 +55,7 @@
   const TableController = function() {
     this.sortAscending = true;
     this.activeSort;
+    this.expandedRows = new Set();
 
     /**
      * Sorts table data
@@ -69,6 +76,33 @@
     };
 
     /**
+     * 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;
+        })
+      }
+      if (this.rowActionsEnabled) {
+        // If table actions are enabled push an empty
+        // string to the header array to account for additional
+        // table actions cell
+        this.header.push({label: '', sortable: false});
+      }
+      if (this.expandable) {
+        // If table is expandable, push an empty string to the
+        // header array to account for additional expansion cell
+        this.header.unshift({label: '', sortable: false});
+      }
+    };
+
+    /**
      * Callback when table row action clicked
      * Emits user desired action and associated row data to
      * parent controller
@@ -100,6 +134,18 @@
     };
 
     /**
+     * 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);
+      }
+    };
+
+    /**
      * onInit Component lifecycle hook
      * Checking for undefined values
      */
@@ -110,6 +156,7 @@
       this.rowActionsEnabled =
           this.rowActionsEnabled === undefined ? false : this.rowActionsEnabled;
       this.size = this.size === undefined ? '' : this.size;
+      this.expandable = this.expandable === undefined ? false : this.expandable;
 
       // Check for undefined 'uiData' property for each item in data array
       this.data = this.data.map((row) => {
@@ -118,21 +165,7 @@
         }
         return row;
       })
-      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;
-        })
-      }
-      if (this.rowActionsEnabled) {
-        // If table actions are enabled push an empty
-        // string to the header array to account for additional
-        // table actions cell
-        this.header.push({label: '', sortable: false});
-      }
+      prepTable();
     };
 
     /**
@@ -165,6 +198,7 @@
       size: '<',               // string
       sortable: '<',           // boolean
       defaultSort: '<',        // number (index of sort)
+      expandable: '<',         // boolean
       emitAction: '&'
     }
   })