Repository: gregnb/mui-datatables
Pull request #559 (2019-04-12 00:23:31)

added the ability to filter columns with array values
Allows the ability to filter based on array values in a single cell.  Addresses Issue #546.
Please let me know if there is anything additional I should do or change.  This is my first time creating a PR and contributing to open source! :smile:  

Example pictures below.

![image](https://user-images.githubusercontent.com/44346401/56004036-19343f80-5c7e-11e9-9db4-3c80361e2250.png)
![image](https://user-images.githubusercontent.com/44346401/56004046-22251100-5c7e-11e9-8cd4-1105551ab58a.png)




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

Coverage decreased (-0.1%) to 71.816% when pulling **d662e33c9b5b1a70862f78049479aa1410688446 on DevelopmentByDavid:issue-546** into **247b6703015edb404730ef39a9c83a760a3225b8 on gregnb:master**.

gregnb: hey @DevelopmentByDavid just returning from a long vacation. Need some time to review. This PR from a quick glance is also asking to make a big change to the way the package accepts data by allowing: strings, numbers, and arrays? 

What would happen if a user places an array but does not use a customBodyRender? how does that display?

I think it's a good idea, but I need some time to think about this major change of allows Array data
DevelopmentByDavid: Hi @gregnb  welcome back! Hope your vacation was good.  

I believe that the way in which I modified the code only effects *filtering* arrays of values in a single cell.  Shown below is the filtering done without the customBodyRender. 

If a person has an array of values in all cells of a column, then they can specify all possible filterOptions (from the docs).  However, my problem was that I had no way to filter that column (the one with arrays as values) appropriately.  I wanted the row to display if it contained any of the _tags_ in that particular cell.

Let me know if I need to add better comments or do something else! Happy and willing to help if I can.
![image](https://user-images.githubusercontent.com/44346401/56082545-dfbf1980-5dce-11e9-83ab-a11118542bcd.png)

DevelopmentByDavid: Hey @gregnb any update on this PR? I'm able to update this more if needed.
gregnb: Hey @DevelopmentByDavid can you add some unit tests for this enhancement? Test a case with arrays and without 
DevelopmentByDavid: Hey @gregnb quick question before I code up the tests.  

Would you rather me modify the data here (link below), which results in me having to modify other tests, or would you rather me build the 2 tests you mentioned with their own set of data?

I think if I modify the original data below it makes sure the changes integrate well with everything else. However, there are a decent number of tests where I have to change what the expected value is.    

Let me know what you prefer/think is better and I will do it.  Thanks!

 https://github.com/gregnb/mui-datatables/blob/b972ad44d5d57c77668017db65eb19d07e788e3c/test/MUIDataTable.test.js#L25-L67,
gregnb: @DevelopmentByDavid I would prefer new tests that way it will be easy to track the new enhancement
DevelopmentByDavid: Hey @gregnb 

So, I changed a few things from my original PR code-wise to be more consistent with how the table already works.  I also added one more enhancement so that the table _just works_ with arrays of data.  Here's the breakdown of everything I added.

- New example  in `array-value-columns/index`
- Filtering based on arrays of data is now more consistent with how the table already works (I'll clarify below)
- `filterData` is extracted from arrays properly, now you don't need to specify `filterOptions` if there are arrays of data in the table -- nice for arrays _just working_.
- Wrote 4 new test: array filtering, array filter option extraction, non-array filtering, non-array filter option extraction

From bullet 2, I realized that all data in the `filterList` is logically AND'd  and that with array data, I was essentially doing logical OR's -- array data is now logically AND'd.  

Please let me know if I need to explain anything further, add more tests, cleanup/modify code, etc.  Thanks!  :smile: 
gregnb: Great! Thanks for making the changes. Merged!

Patch:
diff --git a/examples/array-value-columns/index.js b/examples/array-value-columns/index.js
new file mode 100644
index 0000000..523b466
--- /dev/null
+++ b/examples/array-value-columns/index.js
@@ -0,0 +1,107 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import MUIDataTable from "../../src";
+import Chip from '@material-ui/core/Chip';
+
+class Example extends React.Component {
+
+  render() {
+    // const allTags = ['leave-message', 'frequently-busy', 'nice', 'grumpy', 'in-person', 'preferred', 'second-choice'];
+    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,
+          print: false,
+        }
+      },
+      {
+        name: "Salary",
+        options: {
+          filter: true,
+          sort: false
+        }
+      },
+      {
+        name: 'Tags',
+        options: {
+            filter: true,
+            filterType: 'multiselect',
+            customBodyRender: (value) => {
+                return value.map( (val, key) => {
+                    return <Chip label={val} key={key} />;
+                });
+            }
+        }
+      }      
+    ];
+
+
+    const data = [
+      ["Gabby George", "Business Analyst", "Minneapolis", 30, "$100,000", ['nice', 'preferred']],
+      ["Aiden Lloyd", "Business Consultant", "Dallas",  55, "$200,000", ['grumpy', 'second-choice']],
+      ["Jaden Collins", "Attorney", "Santa Ana", 27, "$500,000", ['frequently-busy', 'leave-message']],
+      ["Franky Rees", "Business Analyst", "St. Petersburg", 22, "$50,000", ['in-person', 'nice']],
+      ["Aaren Rose", "Business Consultant", "Toledo", 28, "$75,000", ['preferred']],
+      ["Blake Duncan", "Business Management Analyst", "San Diego", 65, "$94,000", ['nice']],
+      ["Frankie Parry", "Agency Legal Counsel", "Jacksonville", 71, "$210,000", ['nice', 'preferred']],
+      ["Lane Wilson", "Commercial Specialist", "Omaha", 19, "$65,000", ['frequently-busy', 'leave-message']],
+      ["Robin Duncan", "Business Analyst", "Los Angeles", 20, "$77,000", ['frequently-busy', 'leave-message', 'nice']],
+      ["Mel Brooks", "Business Consultant", "Oklahoma City", 37, "$135,000", ['grumpy', 'second-choice']],
+      ["Harper White", "Attorney", "Pittsburgh", 52, "$420,000", ['preferred']],
+      ["Kris Humphrey", "Agency Legal Counsel", "Laredo", 30, "$150,000", ['preferred']],
+      ["Frankie Long", "Industrial Analyst", "Austin", 31, "$170,000", ['preferred']],
+      ["Brynn Robbins", "Business Analyst", "Norfolk", 22, "$90,000", ['preferred']],
+      ["Justice Mann", "Business Consultant", "Chicago", 24, "$133,000", ['preferred']],
+      ["Addison Navarro", "Business Management Analyst", "New York", 50, "$295,000", ['preferred']],
+      ["Jesse Welch", "Agency Legal Counsel", "Seattle", 28, "$200,000", ['preferred']],
+      ["Eli Mejia", "Commercial Specialist", "Long Beach", 65, "$400,000", ['preferred']],
+      ["Gene Leblanc", "Industrial Analyst", "Hartford", 34, "$110,000", ['preferred']],
+      ["Danny Leon", "Computer Scientist", "Newark", 60, "$220,000", ['preferred']],
+      ["Lane Lee", "Corporate Counselor", "Cincinnati", 52, "$180,000", ['preferred']],
+      ["Jesse Hall", "Business Analyst", "Baltimore", 44, "$99,000", ['preferred']],
+      ["Danni Hudson", "Agency Legal Counsel", "Tampa", 37, "$90,000", ['preferred']],
+      ["Terry Macdonald", "Commercial Specialist", "Miami", 39, "$140,000", ['preferred']],
+      ["Justice Mccarthy", "Attorney", "Tucson", 26, "$330,000", ['preferred']],
+      ["Silver Carey", "Computer Scientist", "Memphis", 47, "$250,000" , ['preferred']],
+      ["Franky Miles", "Industrial Analyst", "Buffalo", 49, "$190,000", ['preferred']],
+      ["Glen Nixon", "Corporate Counselor", "Arlington", 44, "$80,000", ['preferred']],
+      ["Gabby Strickland", "Business Process Consultant", "Scottsdale", 26, "$45,000", ['preferred']],
+      ["Mason Ray", "Computer Scientist", "San Francisco", 39, "$142,000", ['preferred']]
+    ];
+
+    const options = {
+      filter: true,
+      filterType: 'dropdown',
+      responsive: 'scroll',
+    };
+
+    return (
+      <MUIDataTable title={"ACME Employee list"} data={data} columns={columns} options={options} />
+    );
+
+  }
+}
+
+ReactDOM.render(<Example />, document.getElementById("app-root"));
diff --git a/examples/hide-columns-print/index.js b/examples/hide-columns-print/index.js
index 13996ae..be2e6b4 100644
--- a/examples/hide-columns-print/index.js
+++ b/examples/hide-columns-print/index.js
@@ -91,4 +91,4 @@ class Example extends React.Component {
   }
 }
 
-ReactDOM.render(<Example />, document.getElementById("app-root"));
+ReactDOM.render(<Example />, document.getElementById("app-root"));
\ No newline at end of file
diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js
index a76f43e..7a909ca 100644
--- a/src/MUIDataTable.js
+++ b/src/MUIDataTable.js
@@ -60,15 +60,7 @@ const TABLE_LOAD = {
 };
 
 // Populate this list with anything that might render in the toolbar to determine if we hide the toolbar
-const TOOLBAR_ITEMS = [
-  'title',
-  'filter',
-  'search',
-  'print',
-  'download',
-  'viewColumns',
-  'customToolbar'
-];
+const TOOLBAR_ITEMS = ['title', 'filter', 'search', 'print', 'download', 'viewColumns', 'customToolbar'];
 
 const hasToolbarItem = (options, title) => {
   options.title = title;
@@ -402,7 +394,15 @@ class MUIDataTable extends React.Component {
           }
         }
 
-        if (filterData[colIndex].indexOf(value) < 0) filterData[colIndex].push(value);
+        if (filterData[colIndex].indexOf(value) < 0 && !Array.isArray(value)) {
+          filterData[colIndex].push(value);
+        } else if (Array.isArray(value)) {
+          value.forEach(element => {
+            if (filterData[colIndex].indexOf(element) < 0) {
+              filterData[colIndex].push(element);
+            }
+          });
+        }
       }
 
       if (column.filterOptions) {
@@ -442,7 +442,6 @@ class MUIDataTable extends React.Component {
       const sortedData = this.sortTable(tableData, sortIndex, sortDirection);
       tableData = sortedData.data;
     }
-
     /* set source data and display Data set source set */
     this.setState(
       prevState => ({
@@ -503,8 +502,21 @@ class MUIDataTable extends React.Component {
       if (filterVal.length) {
         if (filterType === 'textField' && !this.hasSearchText(columnVal, filterVal, caseSensitive)) {
           isFiltered = true;
-        } else if (filterType !== 'textField' && filterVal.indexOf(columnValue) < 0) {
+        } else if (
+          filterType !== 'textField' &&
+          Array.isArray(columnValue) === false &&
+          filterVal.indexOf(columnValue) < 0
+        ) {
           isFiltered = true;
+        } else if (filterType !== 'textField' && Array.isArray(columnValue)) {
+          //true if every filterVal exists in columnVal, false otherwise
+          const isFullMatch = filterVal.every(el => {
+            return columnValue.indexOf(el) >= 0;
+          });
+          //if it is not a fullMatch, filter row out
+          if (!isFullMatch) {
+            isFiltered = true;
+          }
         }
       }
 
@@ -611,7 +623,6 @@ class MUIDataTable extends React.Component {
         });
       }
     }
-
     return newRows;
   }
 
@@ -1025,22 +1036,24 @@ class MUIDataTable extends React.Component {
             displayData={displayData}
             selectRowUpdate={this.selectRowUpdate}
           />
-        ) : showToolbar && (
-          <TableToolbar
-            columns={columns}
-            displayData={displayData}
-            data={data}
-            filterData={filterData}
-            filterList={filterList}
-            filterUpdate={this.filterUpdate}
-            options={this.options}
-            resetFilters={this.resetFilters}
-            searchTextUpdate={this.searchTextUpdate}
-            tableRef={this.getTableContentRef}
-            title={title}
-            toggleViewColumn={this.toggleViewColumn}
-            setTableAction={this.setTableAction}
-          />
+        ) : (
+          showToolbar && (
+            <TableToolbar
+              columns={columns}
+              displayData={displayData}
+              data={data}
+              filterData={filterData}
+              filterList={filterList}
+              filterUpdate={this.filterUpdate}
+              options={this.options}
+              resetFilters={this.resetFilters}
+              searchTextUpdate={this.searchTextUpdate}
+              tableRef={this.getTableContentRef}
+              title={title}
+              toggleViewColumn={this.toggleViewColumn}
+              setTableAction={this.setTableAction}
+            />
+          )
         )}
         <TableFilterList options={this.options} filterList={filterList} filterUpdate={this.filterUpdate} />
         <div


Test patch:
diff --git a/test/MUIDataTable.test.js b/test/MUIDataTable.test.js
index f207f81..3049669 100644
--- a/test/MUIDataTable.test.js
+++ b/test/MUIDataTable.test.js
@@ -274,7 +274,7 @@ describe('<MUIDataTable />', function() {
       search: false,
       print: false,
       download: false,
-      viewColumns: false
+      viewColumns: false,
     };
 
     const mountWrapper = mount(<MUIDataTable columns={columns} data={data} options={options} />);
@@ -671,6 +671,104 @@ describe('<MUIDataTable />', function() {
     assert.equal(state.displayData.length, data.length);
   });
 
+  describe('should correctly handle array data', () => {
+    const columns = [
+      {
+        name: 'otherData',
+        options: {
+          filter: true,
+        },
+      },
+      {
+        name: 'array',
+        options: {
+          filter: true,
+        },
+      },
+    ];
+    const data = [
+      ['other-data-1', ['a', 'b', 'c']],
+      ['other-data-3', ['a']],
+      ['other-data-2', ['a', 'b']],
+      ['other-data-4', []],
+    ];
+    const options = {
+      filter: true,
+      filterType: 'dropdown',
+      responsive: 'scroll',
+    };
+
+    it('should correctly filter array data', () => {
+      const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />);
+      const table = shallowWrapper.dive();
+      const instance = table.instance();
+      instance.filterUpdate(1, 'b', 'dropdown');
+      table.update();
+      const { displayData } = table.state();
+
+      const expectedResult = JSON.stringify([
+        { data: ['other-data-1', ['a', 'b', 'c']], dataIndex: 0 },
+        { data: ['other-data-2', ['a', 'b']], dataIndex: 2 },
+      ]);
+      assert.deepEqual(JSON.stringify(displayData), expectedResult);
+    });
+    it('should correctly extract array data for filterData', () => {
+      const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />);
+      const table = shallowWrapper.dive();
+      const { filterData } = table.state();
+      const expectedResult = JSON.stringify([
+        ['other-data-1', 'other-data-2', 'other-data-3', 'other-data-4'],
+        ['a', 'b', 'c'],
+      ]);
+      assert.deepEqual(JSON.stringify(filterData), expectedResult);
+    });
+  });
+
+  describe('should correctly handle non-array data', () => {
+    const columns = [
+      {
+        name: 'otherData',
+        options: {
+          filter: true,
+        },
+      },
+      {
+        name: 'non-array',
+        options: {
+          filter: true,
+        },
+      },
+    ];
+    const data = [['other-data-1', 'a'], ['other-data-2', 'b'], ['other-data-3', 'c'], ['other-data-4', 'd']];
+    const options = {
+      filter: true,
+      filterType: 'dropdown',
+      responsive: 'scroll',
+    };
+
+    it('should correctly filter data when no array data is present', () => {
+      const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />);
+      const table = shallowWrapper.dive();
+      const instance = table.instance();
+      instance.filterUpdate(1, 'b', 'dropdown');
+      table.update();
+      const { displayData } = table.state();
+
+      const expectedResult = JSON.stringify([{ data: ['other-data-2', 'b'], dataIndex: 1 }]);
+      assert.deepEqual(JSON.stringify(displayData), expectedResult);
+    });
+    it('should correctly extract filterData when no array data is present', () => {
+      const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />);
+      const table = shallowWrapper.dive();
+      const { filterData } = table.state();
+      const expectedResult = JSON.stringify([
+        ['other-data-1', 'other-data-2', 'other-data-3', 'other-data-4'],
+        ['a', 'b', 'c', 'd'],
+      ]);
+      assert.deepEqual(JSON.stringify(filterData), expectedResult);
+    });
+  });
+
   describe('should correctly run comparator function', () => {
     it('correctly compares two equal strings', () => {
       expect(getCollatorComparator()('testString', 'testString')).to.equal(0);