Repository: gregnb/mui-datatables
Pull request #664 (2019-06-04 21:54:15)

Updates 'options' in componentDidUpdate
In previous version, it looks like options were being updated everytime in componentWillRecieveProps. But it was removed and options were not being applied if they were updated.
I can elaborate the use case if you want.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/23793420/badge)](https://coveralls.io/builds/23793420)

Coverage increased (+0.04%) to 77.258% when pulling **044c7033087678932a6f03af61d4969ab9d8c7de on noobtiger:master** into **2b508722db834843007970c9c8c9c1d9d930cad5 on gregnb:master**.


Patch:
diff --git a/README.md b/README.md
index d9a05af..b7e9c9c 100644
--- a/README.md
+++ b/README.md
@@ -140,10 +140,12 @@ The component accepts the following props:
 |**`textLabels `**|object||User provided labels to localize text
 |**`pagination`**|boolean|true|Enable/disable pagination
 |**`selectableRows`**|string|'multiple'|Numbers of rows that can be selected. Options are "multiple", "single", "none".
+|**`selectableRowsOnClick`**|boolean|false|Enable/disable select toggle when row is clicked. When False, only checkbox will trigger this action.
 |**`isRowSelectable`**|function||Enable/disable selection on certain rows with custom function. Returns true if not provided.  `function(dataIndex) => bool`
-|**`resizableColumns`**|boolean|false|Enable/disable resizable columns
 |**`expandableRows`**|boolean|false|Enable/disable expandable rows
-|**`renderExpandableRow `**|function||Render expandable row. `function(rowData, rowMeta) => React Component`
+|**`expandableRowsOnClick`**|boolean|false|Enable/disable expand trigger when row is clicked. When False, only expand icon will trigger this action.
+|**`renderExpandableRow`**|function||Render expandable row. `function(rowData, rowMeta) => React Component`
+|**`resizableColumns`**|boolean|false|Enable/disable resizable columns
 |**`customToolbar`**|function||Render a custom toolbar
 |**`customToolbarSelect`**|function||Render a custom selected rows toolbar. `function(selectedRows, displayData, setSelectedRows) => void`
 |**`customFooter`**|function||Render a custom table footer. `function(count, page, rowsPerPage, changeRowsPerPage, changePage) => string`&#124;` React Component`
@@ -214,7 +216,7 @@ const columns = [
 |**`empty`**|boolean|false|This denotes whether the column has data or not (for use with intentionally empty columns)
 |**`viewColumns`**|boolean|true|Allow user to toggle column visibility through 'View Column' list
 |**`filterList`**|array||Filter value list [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/column-filters/index.js)
-|**`filterOptions`**|array||Filter options [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/column-filters/index.js)
+|**`filterOptions`**|{names, logic}||With filter options, it's possible to use custom names for the filter fields [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/column-filters/index.js) and/or custom filter logic [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/customize-filter/index.js)
 |**`customFilterListRender`**|function||Function that returns a string used as the chip label. `function(value) => string` [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/column-filters/index.js)
 |**`filter`**|boolean|true|Display column in filter list
 |**`filterType `**|string|'dropdown'|Choice of filtering view. Takes priority over global filterType option.`enum('checkbox', 'dropdown', 'multiselect', 'textField')`.
diff --git a/examples/column-filters/index.js b/examples/column-filters/index.js
index 02c9d5b..f300690 100644
--- a/examples/column-filters/index.js
+++ b/examples/column-filters/index.js
@@ -13,7 +13,9 @@ class Example extends React.Component {
           filter: true,
           filterList: ['Franky Miles'],
           customFilterListRender: v => `Name: ${v}`,
-          filterOptions: ['a', 'b', 'c', 'Business Analyst']
+          filterOptions: {
+            names: ['a', 'b', 'c', 'Business Analyst']
+          },
         }
       },
       {
diff --git a/examples/customize-filter/index.js b/examples/customize-filter/index.js
new file mode 100644
index 0000000..d13f05d
--- /dev/null
+++ b/examples/customize-filter/index.js
@@ -0,0 +1,117 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import MUIDataTable from "../../src";
+
+class Example extends React.Component {
+
+  render() {
+
+    const columns = [
+      {
+        name: "Name",
+        options: {
+          filter: true,
+          display: 'excluded',
+        }
+      },
+      {
+        label: "Modified Title Label",
+        name: "Title",
+        options: {
+          filter: true,
+        }
+      },
+      {
+        name: "Location",
+        options: {
+          print: false,
+          filter: false,
+        }
+      },
+      {
+        name: "Age",
+        options: {
+          filter: true,
+          filterOptions: {
+            names: ["young", "adult", "middle-age", "senior", "elderly"],
+            logic(age, filters) {
+              const show = (filters.indexOf("young") >= 0 && age <= 35) ||
+                (filters.indexOf("adult") >= 0 && age > 35 && age <= 45) ||
+                (filters.indexOf("middle-age") >= 0 && age > 45 && age <= 65) ||
+                (filters.indexOf("senior") >= 0 && age > 65 && age <= 75) ||
+                (filters.indexOf("elderly") >= 0 && age > 75);
+              const filtered = !show;
+              return filtered;
+            }
+          },
+          print: false,
+        }
+      },
+      {
+        name: "Salary",
+        options: {
+          filter: true,
+          filterType: "checkbox",
+          filterOptions: {
+            names: ["Lower wages", "Average wages", "Higher wages"],
+            logic(salary, filterVal) {
+              salary = salary.replace(/[^\d]/g, "");
+              const show = (filterVal.indexOf("Lower wages") >= 0 && salary < 100000) ||
+                (filterVal.indexOf("Average wages") >= 0 && salary >= 100000 && salary < 200000) ||
+                (filterVal.indexOf("Higher wages") >= 0 && salary >= 200000);
+              return !show;
+            }
+          },
+          sort: false
+        }
+      }
+    ];
+
+
+    const data = [
+      ["Gabby George", "Business Analyst", "Minneapolis", 30, "$100,000"],
+      ["Aiden Lloyd", "Business Consultant", "Dallas", 55, "$200,000"],
+      ["Jaden Collins", "Attorney", "Santa Ana", 27, "$500,000"],
+      ["Franky Rees", "Business Analyst", "St. Petersburg", 22, "$50,000"],
+      ["Aaren Rose", "Business Consultant", "Toledo", 28, "$75,000"],
+      ["Blake Duncan", "Business Management Analyst", "San Diego", 65, "$94,000"],
+      ["Frankie Parry", "Agency Legal Counsel", "Jacksonville", 71, "$210,000"],
+      ["Lane Wilson", "Commercial Specialist", "Omaha", 19, "$65,000"],
+      ["Robin Duncan", "Business Analyst", "Los Angeles", 20, "$77,000"],
+      ["Mel Brooks", "Business Consultant", "Oklahoma City", 37, "$135,000"],
+      ["Harper White", "Attorney", "Pittsburgh", 52, "$420,000"],
+      ["Kris Humphrey", "Agency Legal Counsel", "Laredo", 30, "$150,000"],
+      ["Frankie Long", "Industrial Analyst", "Austin", 31, "$170,000"],
+      ["Brynn Robbins", "Business Analyst", "Norfolk", 22, "$90,000"],
+      ["Justice Mann", "Business Consultant", "Chicago", 24, "$133,000"],
+      ["Addison Navarro", "Business Management Analyst", "New York", 50, "$295,000"],
+      ["Jesse Welch", "Agency Legal Counsel", "Seattle", 28, "$200,000"],
+      ["Eli Mejia", "Commercial Specialist", "Long Beach", 65, "$400,000"],
+      ["Gene Leblanc", "Industrial Analyst", "Hartford", 34, "$110,000"],
+      ["Danny Leon", "Computer Scientist", "Newark", 60, "$220,000"],
+      ["Lane Lee", "Corporate Counselor", "Cincinnati", 52, "$180,000"],
+      ["Jesse Hall", "Business Analyst", "Baltimore", 44, "$99,000"],
+      ["Danni Hudson", "Agency Legal Counsel", "Tampa", 37, "$90,000"],
+      ["Terry Macdonald", "Commercial Specialist", "Miami", 39, "$140,000"],
+      ["Justice Mccarthy", "Attorney", "Tucson", 26, "$330,000"],
+      ["Silver Carey", "Computer Scientist", "Memphis", 47, "$250,000"],
+      ["Franky Miles", "Industrial Analyst", "Buffalo", 49, "$190,000"],
+      ["Glen Nixon", "Corporate Counselor", "Arlington", 44, "$80,000"],
+      ["Gabby Strickland", "Business Process Consultant", "Scottsdale", 26, "$45,000"],
+      ["Mason Ray", "Computer Scientist", "San Francisco", 39, "$142,000"]
+    ];
+
+    const options = {
+      filter: true,
+      filterType: 'dropdown',
+      responsive: 'scroll',
+    };
+
+    return (
+      <MUIDataTable title={"ACME Employee list - customizeFilter"} data={data} columns={columns} options={options} />
+    );
+
+  }
+}
+
+ReactDOM.render(<Example />, document.getElementById("app-root"));
\ No newline at end of file
diff --git a/examples/data-as-objects/index.js b/examples/data-as-objects/index.js
index 255db50..e4c9031 100644
--- a/examples/data-as-objects/index.js
+++ b/examples/data-as-objects/index.js
@@ -43,19 +43,27 @@ class Example extends React.Component {
           filter: true,
           sort: false
         }
-      }      
+      },
+      {
+        name: "phone.home",
+        label: "Home Phone",
+      },
+      {
+        name: "phone.fake",
+        label: "Not a Phone #",
+      },
+      {
+        name: "phone2.home",
+        label: "Not An Attribute",
+      }
     ];
 
 
     const data = [
-      { name: "Gabby George", title: "Business Analyst", location: "Minneapolis", age: 30, salary: "$100,000" },
-      { name: "Aiden Lloyd", title: "Business Consultant", location: "Dallas",  age: 55, salary: "$200,000" },
-      { name: "Jaden Collins", title: "Attorney", location: "Santa Ana", age: 27, salary: "$500,000" },
-      { name: "Franky Rees", title: "Business Analyst", location: "St. Petersburg", age: 22, salary: "$50,000" }
-      // ["Aaren Rose", "Business Consultant", "Toledo", 28, "$75,000"],
-      // ["Blake Duncan", "Business Management Analyst", "San Diego", 65, "$94,000"],
-      // ["Frankie Parry", "Agency Legal Counsel", "Jacksonville", 71, "$210,000"],
-      // ["Lane Wilson", "Commercial Specialist", "Omaha", 19, "$65,000"]
+      { name: "Gabby George", title: "Business Analyst", location: "Minneapolis", age: 30, salary: "$100,000", phone: { home: '867-5309', cell: '123-4567' } },
+      { name: "Aiden Lloyd", title: "Business Consultant", location: "Dallas",  age: 55, salary: "$200,000", phone: { home: '867-5310', cell: '123-4568' } },
+      { name: "Jaden Collins", title: "Attorney", location: "Santa Ana", age: 27, salary: "$500,000", phone: { home: '867-5311', cell: '123-4569' } },
+      { name: "Franky Rees", title: "Business Analyst", location: "St. Petersburg", age: 22, salary: "$50,000", phone: { home: '867-5312', cell: '123-4569' } }
     ];
 
     const options = {
diff --git a/examples/expandable-rows/index.js b/examples/expandable-rows/index.js
index 9e1aff7..5d3eba3 100644
--- a/examples/expandable-rows/index.js
+++ b/examples/expandable-rows/index.js
@@ -14,7 +14,7 @@ class Example extends React.Component {
         options: {
           filter: true,
         }
-      },      
+      },
       {
         name: "Title",
         options: {
@@ -39,7 +39,7 @@ class Example extends React.Component {
           filter: true,
           sort: false
         }
-      }      
+      }
     ];
 
 
@@ -81,6 +81,7 @@ class Example extends React.Component {
       filterType: 'dropdown',
       responsive: 'scroll',
       expandableRows: true,
+      expandableRowsOnClick: true,
       renderExpandableRow: (rowData, rowMeta) => {
         const colSpan = rowData.length + 1;
         return (
diff --git a/examples/resizable-columns/index.js b/examples/resizable-columns/index.js
index 99084f2..ab5a891 100644
--- a/examples/resizable-columns/index.js
+++ b/examples/resizable-columns/index.js
@@ -29,8 +29,19 @@ class Example extends React.Component {
           customBodyRender: value => <button onClick={this.update}>+</button>
         }
       },
-      "Name",
-      "Title",
+      {
+        name: "Name",
+        options: {
+          sort: false,
+          hint: "?"
+        }
+      },
+      {
+        name: "Title",
+        options: {
+          hint: "?"
+        }
+      },
       "Location"
     ];
 
diff --git a/examples/serverside-pagination/index.js b/examples/serverside-pagination/index.js
index 32061ff..9b74cfc 100644
--- a/examples/serverside-pagination/index.js
+++ b/examples/serverside-pagination/index.js
@@ -1,13 +1,13 @@
 import React from "react";
 import ReactDOM from "react-dom";
-import { CircularProgress } from '@material-ui/core';
+import { CircularProgress, Typography } from '@material-ui/core';
 import MUIDataTable from "../../src/";
 
 class Example extends React.Component {
 
   state = {
     page: 0,
-    count: 100,
+    count: 1,
     data: [["Loading Data..."]],
     isLoading: false
   };
@@ -19,8 +19,8 @@ class Example extends React.Component {
   // get data
   getData = () => {
     this.setState({ isLoading: true });
-    this.xhrRequest().then(data => {
-      this.setState({ data, isLoading: false });
+    this.xhrRequest().then(res => {
+      this.setState({ data: res.data, isLoading: false, count: res.total });
     });
   }
 
@@ -28,6 +28,8 @@ class Example extends React.Component {
   xhrRequest = () => {
 
     return new Promise((resolve, reject) => {
+      const total = 124;  // mock record count from server
+      // mock page data
       const srcData = [
         ["Gabby George", "Business Analyst", "Minneapolis"],
         ["Aiden Lloyd", "Business Consultant", "Dallas"],
@@ -35,13 +37,14 @@ class Example extends React.Component {
         ["Franky Rees", "Business Analyst", "St. Petersburg"],
         ["Aaren Rose", "Business Analyst", "Toledo"]
       ];
-
       const maxRound =  Math.floor(Math.random() * 2) + 1;
       const data = [...Array(maxRound)].reduce(acc => acc.push(...srcData) && acc, []);
       data.sort((a, b) => 0.5 - Math.random());
 
       setTimeout(() => {
-        resolve(data);
+        resolve({
+          data, total
+        });
       }, 2500);
 
     });
@@ -49,10 +52,15 @@ class Example extends React.Component {
   }
 
   changePage = (page) => {
-    this.xhrRequest(`/myApiServer?page=${page}`).then(data => {
+    this.setState({
+      isLoading: true,
+    });
+    this.xhrRequest(`/myApiServer?page=${page}`).then(res => {
       this.setState({
+        isLoading: false,
         page: page,
-        data
+        data: res.data,
+        count: res.total,
       });
     });
   };
@@ -82,11 +90,13 @@ class Example extends React.Component {
         }
       }
     };
-
     return (
       <div>
-        {isLoading && <CircularProgress style={{ marginLeft: '50%' }} />}
-        <MUIDataTable title={"ACME Employee list"} data={data} columns={columns} options={options} />
+        <MUIDataTable title={<Typography variant="title">
+          ACME Employee list
+          {isLoading && <CircularProgress size={24} style={{marginLeft: 15, position: 'relative', top: 4}} />}
+          </Typography>
+          } data={data} columns={columns} options={options} />
       </div>
     );
 
diff --git a/package-lock.json b/package-lock.json
index c769157..842e7d3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "mui-datatables",
-  "version": "2.3.0",
+  "version": "2.4.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -4522,13 +4522,15 @@
           "version": "1.0.0",
           "resolved": false,
           "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "resolved": false,
           "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
           "dev": true,
+          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -4545,19 +4547,22 @@
           "version": "1.1.0",
           "resolved": false,
           "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
           "resolved": false,
           "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "resolved": false,
           "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -4688,7 +4693,8 @@
           "version": "2.0.3",
           "resolved": false,
           "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -4702,6 +4708,7 @@
           "resolved": false,
           "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
           "dev": true,
+          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -4718,6 +4725,7 @@
           "resolved": false,
           "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
           "dev": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -4726,13 +4734,15 @@
           "version": "0.0.8",
           "resolved": false,
           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "minipass": {
           "version": "2.2.4",
           "resolved": false,
           "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==",
           "dev": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.1",
             "yallist": "^3.0.0"
@@ -4753,6 +4763,7 @@
           "resolved": false,
           "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
           "dev": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -4841,7 +4852,8 @@
           "version": "1.0.1",
           "resolved": false,
           "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -4855,6 +4867,7 @@
           "resolved": false,
           "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
           "dev": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -4992,6 +5005,7 @@
           "resolved": false,
           "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
           "dev": true,
+          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
diff --git a/package.json b/package.json
index 511c98d..a86b010 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "mui-datatables",
-  "version": "2.3.0",
+  "version": "2.4.0",
   "description": "Datatables for React using Material-UI",
   "main": "dist/index.js",
   "files": [
diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js
index a5120dc..24fac9c 100644
--- a/src/MUIDataTable.js
+++ b/src/MUIDataTable.js
@@ -91,7 +91,13 @@ class MUIDataTable extends React.Component {
             download: PropTypes.bool,
             viewColumns: PropTypes.bool,
             filterList: PropTypes.array,
-            filterOptions: PropTypes.array,
+            filterOptions: PropTypes.oneOfType([
+              PropTypes.array,
+              PropTypes.shape({
+                names: PropTypes.array,
+                logic: PropTypes.func,
+              }),
+            ]),
             filterType: PropTypes.oneOf(['dropdown', 'checkbox', 'multiselect', 'textField']),
             customHeadRender: PropTypes.func,
             customBodyRender: PropTypes.func,
@@ -107,6 +113,7 @@ class MUIDataTable extends React.Component {
       textLabels: PropTypes.object,
       pagination: PropTypes.bool,
       expandableRows: PropTypes.bool,
+      expandableRowsOnClick: PropTypes.bool,
       renderExpandableRow: PropTypes.func,
       customToolbar: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
       customToolbarSelect: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
@@ -115,6 +122,7 @@ class MUIDataTable extends React.Component {
       onRowClick: PropTypes.func,
       resizableColumns: PropTypes.bool,
       selectableRows: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['none', 'single', 'multiple'])]),
+      selectableRowsOnClick: PropTypes.bool,
       isRowSelectable: PropTypes.func,
       serverSide: PropTypes.bool,
       onTableChange: PropTypes.func,
@@ -160,6 +168,7 @@ class MUIDataTable extends React.Component {
     displayData: [],
     page: 0,
     rowsPerPage: 0,
+    count: 0,
     columns: [],
     filterData: [],
     filterList: [],
@@ -200,6 +209,7 @@ class MUIDataTable extends React.Component {
       this.setTableData(this.props, TABLE_LOAD.INITIAL, () => {
         this.setTableAction('propsUpdate');
       });
+      this.updateOptions(this.props);
     }
 
     if (this.props.options.searchText !== prevProps.options.searchText) {
@@ -213,6 +223,10 @@ class MUIDataTable extends React.Component {
     }
   }
 
+  updateOptions(props) {
+    this.options = merge(this.options, props.options);
+  }
+
   initializeTable(props) {
     this.getDefaultOptions(props);
     this.setTableOptions(props);
@@ -231,8 +245,10 @@ class MUIDataTable extends React.Component {
       pagination: true,
       textLabels,
       expandableRows: false,
+      expandableRowsOnClick: false,
       resizableColumns: false,
       selectableRows: 'multiple',
+      selectableRowsOnClick: false,
       caseSensitive: false,
       serverSide: false,
       rowHover: true,
@@ -375,6 +391,8 @@ class MUIDataTable extends React.Component {
   };
 
   transformData = (columns, data) => {
+    const leaf = (obj, path) => (path.split('.').reduce((value, el) => value ? value[el] : undefined, obj));
+
     return Array.isArray(data[0])
       ? data.map(row => {
           let i = -1;
@@ -384,7 +402,7 @@ class MUIDataTable extends React.Component {
             return col.empty ? undefined : row[i];
           });
         })
-      : data.map(row => columns.map(col => row[col.name]));
+      : data.map(row => columns.map(col => leaf(row, col.name)));
   };
 
   setTableData(props, status, callback = () => {}) {
@@ -432,7 +450,14 @@ class MUIDataTable extends React.Component {
       }
 
       if (column.filterOptions) {
-        filterData[colIndex] = cloneDeep(column.filterOptions);
+        if (Array.isArray(column.filterOptions)) {
+          filterData[colIndex] = cloneDeep(column.filterOptions);
+          console.error(
+            'Deprecated: filterOptions must now be an object. see https://github.com/gregnb/mui-datatables/tree/master/examples/customize-filter example',
+          );
+        } else if (Array.isArray(column.filterOptions.names)) {
+          filterData[colIndex] = cloneDeep(column.filterOptions.names);
+        }
       }
 
       if (column.filterList) {
@@ -485,6 +510,7 @@ class MUIDataTable extends React.Component {
         filterList: filterList,
         searchText: searchText,
         selectedRows: selectedRowsData,
+        count: options.count,
         data: tableData,
         displayData: this.getDisplayData(columns, tableData, filterList, searchText),
       }),
@@ -536,7 +562,9 @@ class MUIDataTable extends React.Component {
       const caseSensitive = this.options.caseSensitive;
       const filterType = column.filterType || this.options.filterType;
       if (filterVal.length) {
-        if (filterType === 'textField' && !this.hasSearchText(columnVal, filterVal, caseSensitive)) {
+        if (column.filterOptions && column.filterOptions.logic) {
+          if (column.filterOptions.logic(columnValue, filterVal)) isFiltered = true;
+        } else if (filterType === 'textField' && !this.hasSearchText(columnVal, filterVal, caseSensitive)) {
           isFiltered = true;
         } else if (
           filterType !== 'textField' &&
@@ -1064,7 +1092,7 @@ class MUIDataTable extends React.Component {
       searchText,
     } = this.state;
 
-    const rowCount = this.options.count || displayData.length;
+    const rowCount = this.state.count || displayData.length;
     const rowsPerPage = this.options.pagination ? this.state.rowsPerPage : displayData.length;
     const showToolbar = hasToolbarItem(this.options, title);
     const columnNames = columns.map(column => ({ name: column.name }));
diff --git a/src/components/TableBody.js b/src/components/TableBody.js
index 6a0e9df..e47e9b9 100644
--- a/src/components/TableBody.js
+++ b/src/components/TableBody.js
@@ -105,10 +105,39 @@ class TableBody extends React.Component {
   };
 
   handleRowClick = (row, data, event) => {
-    // don't trigger onRowClick if the event was actually a row selection
+    // Don't trigger onRowClick if the event was actually the expandable icon
+    if (
+      event.target.id === 'expandable-button' ||
+      (event.target.nodeName === 'path' && event.target.parentNode.id === 'expandable-button')
+    ) {
+      // In a future release, onRowClick will no longer be called here (for consistency).
+      // For now, issue a deprecated warning.
+      if (this.props.options.onRowClick) {
+        console.warn(
+          'Deprecated: Clicks on expandable button will not trigger onRowClick in an upcoming release, see: https://github.com/gregnb/mui-datatables/issues/516.',
+        );
+        this.props.options.onRowClick(row, data, event);
+      }
+
+      return;
+    }
+
+    // Don't trigger onRowClick if the event was actually a row selection
     if (event.target.id && event.target.id.startsWith('MUIDataTableSelectCell')) {
       return;
     }
+
+    // Check if we should toggle row select when row is clicked anywhere
+    if (this.props.options.selectableRowsOnClick && this.props.options.selectableRows !== 'none') {
+      const selectRow = { index: data.rowIndex, dataIndex: data.dataIndex };
+      this.handleRowSelect(selectRow);
+    }
+    // Check if we should trigger row expand when row is clicked anywhere
+    if (this.props.options.expandableRowsOnClick && this.props.options.expandableRows) {
+      const expandRow = { index: data.rowIndex, dataIndex: data.dataIndex };
+      this.props.toggleExpandRow(expandRow);
+    }
+
     this.props.options.onRowClick && this.props.options.onRowClick(row, data, event);
   };
 
diff --git a/src/components/TableBodyRow.js b/src/components/TableBodyRow.js
index 8594ae4..044e419 100644
--- a/src/components/TableBodyRow.js
+++ b/src/components/TableBodyRow.js
@@ -7,6 +7,7 @@ import { withStyles } from '@material-ui/core/styles';
 const defaultBodyRowStyles = theme => ({
   root: {},
   hover: {},
+  hoverCursor: { cursor: 'pointer' },
   responsiveStacked: {
     [theme.breakpoints.down('sm')]: {
       border: 'solid 2px rgba(0, 0, 0, 0.15)',
@@ -37,6 +38,7 @@ class TableBodyRow extends React.Component {
           {
             [classes.root]: true,
             [classes.hover]: options.rowHover,
+            [classes.hoverCursor]: options.selectableRowsOnClick || options.expandableRowsOnClick,
             [classes.responsiveStacked]: options.responsive === 'stacked',
           },
           className,
diff --git a/src/components/TableFilter.js b/src/components/TableFilter.js
index ce95058..02f033a 100644
--- a/src/components/TableFilter.js
+++ b/src/components/TableFilter.js
@@ -247,7 +247,7 @@ class TableFilter extends React.Component {
                 <MenuItem value={filterValue} key={filterIndex + 1}>
                   <Checkbox
                     checked={filterList[index].indexOf(filterValue) >= 0 ? true : false}
-                    value={filterValue !== null ? filterValue.toString() : ''}
+                    value={filterValue != null ? filterValue.toString() : ''}
                     className={classes.checkboxIcon}
                     classes={{
                       root: classes.checkbox,
@@ -291,7 +291,7 @@ class TableFilter extends React.Component {
           </div>
           <div className={classes.filtersSelected} />
         </div>
-        <GridList cellHeight="auto" cols={filterGridColumns} cellHeight={64} spacing={34}>
+        <GridList cellHeight="auto" cols={filterGridColumns} cellHeight={70} spacing={34}>
           {columns.map((column, index) => {
             if (column.filter) {
               const filterType = column.filterType || options.filterType;
diff --git a/src/components/TableHeadCell.js b/src/components/TableHeadCell.js
index 4eec2db..0708d38 100644
--- a/src/components/TableHeadCell.js
+++ b/src/components/TableHeadCell.js
@@ -28,17 +28,16 @@ const defaultHeadCellStyles = theme => ({
     display: 'inline-block',
   },
   sortAction: {
-    display: 'inline-block',
+    display: 'flex',
     verticalAlign: 'top',
     cursor: 'pointer',
-    paddingLeft: '4px',
     height: '10px',
-    position: 'absolute',
   },
   sortActive: {
     color: theme.palette.text.primary,
   },
   toolButton: {
+    display: 'flex',
     height: '10px',
     outline: 'none',
     cursor: 'pointer',
@@ -63,16 +62,23 @@ class TableHeadCell extends React.Component {
     print: PropTypes.bool.isRequired,
   };
 
+  state = {
+    isSortTooltipOpen: false,
+    isHintTooltipOpen: false,
+  };
+
   handleSortClick = () => {
     this.props.toggleSort(this.props.index);
   };
 
   render() {
+    const { isSortTooltipOpen, isHintTooltipOpen } = this.state;
     const { children, classes, options, sortDirection, sort, hint, print } = this.props;
     const sortActive = sortDirection !== null && sortDirection !== undefined ? true : false;
 
     const sortLabelProps = {
       active: sortActive,
+      hideSortIcon: true,
       ...(sortDirection ? { direction: sortDirection } : {}),
     };
 
@@ -87,12 +93,19 @@ class TableHeadCell extends React.Component {
         {options.sort && sort ? (
           <Tooltip
             title={options.textLabels.body.toolTip}
-            placement={'bottom-end'}
+            placement={'bottom-start'}
             classes={{
               tooltip: classes.tooltip,
             }}
             enterDelay={300}
-            classes={{ popper: classes.mypopper }}>
+            classes={{ popper: classes.mypopper }}
+            open={isSortTooltipOpen}
+            onOpen={() =>
+              isHintTooltipOpen
+                ? this.setState({ isSortTooltipOpen: false })
+                : this.setState({ isSortTooltipOpen: true })
+            }
+            onClose={() => this.setState({ isSortTooltipOpen: false })}>
             <span
               role="button"
               onKeyUp={this.handleClickSort}
@@ -108,24 +121,40 @@ class TableHeadCell extends React.Component {
               </div>
               <div className={classes.sortAction}>
                 <TableSortLabel {...sortLabelProps} />
+                {hint && (
+                  <Tooltip
+                    title={hint}
+                    placement={'bottom-end'}
+                    classes={{
+                      tooltip: classes.tooltip,
+                    }}
+                    enterDelay={300}
+                    classes={{ popper: classes.mypopper }}
+                    open={isHintTooltipOpen}
+                    onOpen={() => this.setState({ isSortTooltipOpen: false, isHintTooltipOpen: true })}
+                    onClose={() => this.setState({ isHintTooltipOpen: false })}>
+                    <HelpIcon fontSize="small" />
+                  </Tooltip>
+                )}
               </div>
             </span>
           </Tooltip>
         ) : (
           children
         )}
-        {hint && (
-          <Tooltip
-            title={hint}
-            placement={'bottom-end'}
-            classes={{
-              tooltip: classes.tooltip,
-            }}
-            enterDelay={300}
-            classes={{ popper: classes.mypopper }}>
-            <HelpIcon fontSize="small" />
-          </Tooltip>
-        )}
+        {!options.sort ||
+          (!sort && hint && (
+            <Tooltip
+              title={hint}
+              placement={'bottom-end'}
+              classes={{
+                tooltip: classes.tooltip,
+              }}
+              enterDelay={300}
+              classes={{ popper: classes.mypopper }}>
+              <HelpIcon fontSize="small" />
+            </Tooltip>
+          ))}
       </TableCell>
     );
   }
diff --git a/src/components/TableSelectCell.js b/src/components/TableSelectCell.js
index 0c773f4..f22f328 100644
--- a/src/components/TableSelectCell.js
+++ b/src/components/TableSelectCell.js
@@ -113,7 +113,7 @@ class TableSelectCell extends React.Component {
     return (
       <TableCell className={cellClass} padding="checkbox">
         <div style={{ display: 'flex', alignItems: 'center' }}>
-          {expandableOn && <KeyboardArrowRight className={iconClass} onClick={onExpand} />}
+          {expandableOn && <KeyboardArrowRight id="expandable-button" className={iconClass} onClick={onExpand} />}
           {selectableOn !== 'none' && renderCheckBox()}
         </div>
       </TableCell>
diff --git a/webpack.config.js b/webpack.config.js
index 6a2037e..b98ca64 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,7 +3,7 @@ const webpack = require('webpack');
 
 module.exports = {
   entry: {
-    app: "./examples/column-filters/index.js"
+    app: "./examples/customize-filter/index.js"
   },
   stats: "verbose",
   context: __dirname,


Test patch:
diff --git a/test/MUIDataTable.test.js b/test/MUIDataTable.test.js
index 512c9e2..f73661e 100644
--- a/test/MUIDataTable.test.js
+++ b/test/MUIDataTable.test.js
@@ -371,7 +371,6 @@ describe('<MUIDataTable />', function() {
     const instance = table.instance();
     instance.filterUpdate(0, 'Joe James', 'Name', 'checkbox');
     table.update();
-
     const state = table.state();
     assert.deepEqual(state.filterList, [['Joe James'], [], [], [], []]);
   });
@@ -936,6 +935,90 @@ describe('<MUIDataTable />', function() {
     assert.equal(state.displayData.length, data.length);
   });
 
+  describe('should displayData consider filterOptions with logic', () => {
+    it('with one column', () => {
+      const customFilterColumns = columns.map(c => {
+        if (c.name === 'Name')
+          return {
+            ...c,
+            options: {
+              ...c.options,
+              filterOptions: {
+                names: ['B', 'J'],
+                logic(name, filters) {
+                  const firstLetter = name[0];
+                  return firstLetter !== filters[0];
+                },
+              },
+            },
+          };
+        return c;
+      });
+
+      const shallowWrapper = shallow(<MUIDataTable columns={customFilterColumns} data={data} />);
+      const table = shallowWrapper.dive();
+      const instance = table.instance();
+      instance.filterUpdate(0, 'J', 'checkbox');
+      table.update();
+
+      const state = table.state();
+      assert.deepEqual(state.filterList, [['J'], [], [], [], []]);
+
+      const expectedResult = JSON.stringify([
+        { data: ['James, Joe', 'Test Corp', renderCities('Yonkers', { rowIndex: 0 }), 'NY', undefined], dataIndex: 0 },
+      ]);
+      assert.deepEqual(JSON.stringify(state.displayData), expectedResult);
+    });
+
+    it('operating as AND', () => {
+      const customFilterColumns = columns.map(c => {
+        if (c.name === 'Name')
+          return {
+            ...c,
+            options: {
+              ...c.options,
+              filterOptions: {
+                names: ['B', 'J', 'H'],
+                logic(name, filters) {
+                  const firstLetter = name[0];
+                  return firstLetter !== filters[0];
+                },
+              },
+            },
+          };
+        if (c.name === 'State')
+          return {
+            ...c,
+            options: {
+              ...c.options,
+              filterOptions: {
+                names: ['NY', 'FL', 'TX'],
+              },
+            },
+          };
+        return c;
+      });
+
+      const shallowWrapper = shallow(<MUIDataTable columns={customFilterColumns} data={data} />);
+      const table = shallowWrapper.dive();
+      const instance = table.instance();
+      instance.filterUpdate(0, 'H', 'checkbox');
+      instance.filterUpdate(3, 'TX', 'checkbox');
+      table.update();
+
+      const state = table.state();
+      assert.deepEqual(state.filterList, [['H'], [], [], ['TX'], []]);
+
+      const expectedResult = JSON.stringify([
+        {
+          data: ['Houston, James', 'Test Corp', renderCities('Dallas', { rowIndex: 3 }), 'TX', undefined],
+          dataIndex: 3,
+        },
+      ]);
+      assert.deepEqual(JSON.stringify(state.displayData), expectedResult);
+    });
+  });
+
   describe('should correctly handle array data', () => {
     const columns = [
       {
diff --git a/test/MUIDataTableBody.test.js b/test/MUIDataTableBody.test.js
index b908784..2fb80ac 100644
--- a/test/MUIDataTableBody.test.js
+++ b/test/MUIDataTableBody.test.js
@@ -202,6 +202,142 @@ describe('<TableBody />', function() {
     assert.strictEqual(selectRowUpdate.callCount, 1);
   });
 
+  it('should gather selected row data when clicking row with selectableRowsOnClick=true.', () => {
+    let selectedRowData;
+    const options = { selectableRows: true, selectableRowsOnClick: true };
+    const selectRowUpdate = (type, data) => (selectedRowData = data);
+    const toggleExpandRow = spy();
+
+    const mountWrapper = mount(
+      <TableBody
+        data={displayData}
+        count={displayData.length}
+        columns={columns}
+        page={0}
+        rowsPerPage={10}
+        selectedRows={[]}
+        selectRowUpdate={selectRowUpdate}
+        expandedRows={[]}
+        toggleExpandRow={toggleExpandRow}
+        options={options}
+        searchText={''}
+        filterList={[]}
+      />,
+    );
+
+    mountWrapper
+      .find('#MUIDataTableBodyRow-2')
+      .first()
+      .simulate('click');
+
+    const expectedResult = { index: 2, dataIndex: 2 };
+    assert.deepEqual(selectedRowData, expectedResult);
+    assert.strictEqual(toggleExpandRow.callCount, 0);
+  });
+
+  it('should gather expanded row data when clicking row with expandableRows=true and expandableRowsOnClick=true.', () => {
+    let expandedRowData;
+    const options = { selectableRows: true, expandableRows: true, expandableRowsOnClick: true };
+    const selectRowUpdate = spy();
+    const toggleExpandRow = data => (expandedRowData = data);
+
+    const mountWrapper = mount(
+      <TableBody
+        data={displayData}
+        count={displayData.length}
+        columns={columns}
+        page={0}
+        rowsPerPage={10}
+        selectedRows={[]}
+        selectRowUpdate={selectRowUpdate}
+        expandedRows={[]}
+        toggleExpandRow={toggleExpandRow}
+        options={options}
+        searchText={''}
+        filterList={[]}
+      />,
+    );
+
+    mountWrapper
+      .find('#MUIDataTableBodyRow-2')
+      .first()
+      .simulate('click');
+
+    const expectedResult = { index: 2, dataIndex: 2 };
+    assert.deepEqual(expandedRowData, expectedResult);
+    assert.strictEqual(selectRowUpdate.callCount, 0);
+  });
+
+  it('should gather both selected and expanded row data when clicking row with expandableRows=true, selectableRowsOnClick=true, and expandableRowsOnClick=true.', () => {
+    let expandedRowData;
+    let selectedRowData;
+    const options = {
+      selectableRows: true,
+      selectableRowsOnClick: true,
+      expandableRows: true,
+      expandableRowsOnClick: true,
+    };
+    const selectRowUpdate = (type, data) => (selectedRowData = data);
+    const toggleExpandRow = data => (expandedRowData = data);
+
+    const mountWrapper = mount(
+      <TableBody
+        data={displayData}
+        count={displayData.length}
+        columns={columns}
+        page={0}
+        rowsPerPage={10}
+        selectedRows={[]}
+        selectRowUpdate={selectRowUpdate}
+        expandedRows={[]}
+        toggleExpandRow={toggleExpandRow}
+        options={options}
+        searchText={''}
+        filterList={[]}
+      />,
+    );
+
+    mountWrapper
+      .find('#MUIDataTableBodyRow-2')
+      .first()
+      .simulate('click');
+
+    const expectedResult = { index: 2, dataIndex: 2 };
+    assert.deepEqual(selectedRowData, expectedResult);
+    assert.deepEqual(expandedRowData, expectedResult);
+  });
+
+  it('should not call onRowClick when clicking on checkbox for selectable row', () => {
+    const options = { selectableRows: true, onRowClick: spy() };
+    const selectRowUpdate = spy();
+    const toggleExpandRow = spy();
+
+    const mountWrapper = mount(
+      <TableBody
+        data={displayData}
+        count={displayData.length}
+        columns={columns}
+        page={0}
+        rowsPerPage={10}
+        selectedRows={[]}
+        selectRowUpdate={selectRowUpdate}
+        expandedRows={[]}
+        toggleExpandRow={toggleExpandRow}
+        options={options}
+        searchText={''}
+        filterList={[]}
+      />,
+    );
+
+    mountWrapper
+      .find('TableSelectCell')
+      .first()
+      .find('input')
+      .simulate('click');
+
+    assert.strictEqual(options.onRowClick.callCount, 0);
+  });
+
   it('should call onRowClick when Row is clicked', () => {
     const options = { selectableRows: true, onRowClick: spy() };
     const selectRowUpdate = stub();