Repository: gregnb/mui-datatables
Pull request #346 (2019-01-07 01:16:43)

Adding "textField" filter type (#342)
Adds a `textField` type to the `filterType` options. 

This PR allows users to select `textFeild` for the filter type, which will allows users to start typing to filter a particular column.

Here is what it looks like when the filter is active:

![filter_active](https://user-images.githubusercontent.com/3633332/50744289-ef3b2f00-11ef-11e9-8ce9-f729945c1375.png)

Here is what it looks like when the filter tab is open:

![filter-open](https://user-images.githubusercontent.com/3633332/50744297-f7936a00-11ef-11e9-8217-ae3f4774ed73.png)


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

Coverage increased (+0.4%) to 73.258% when pulling **c798117f14e29b60e52a4e76470b031eaf339275 on loganasherjones:textFieldFilter** into **50e73ae2cc9168abbe7560175d84590b9a884942 on gregnb:master**.


Patch:
diff --git a/README.md b/README.md
index 2e1fee2..a3ead5a 100644
--- a/README.md
+++ b/README.md
@@ -131,7 +131,7 @@ The component accepts the following props:
 |**`count`**|number||User provided override for total number of rows
 |**`serverSide`**|boolean|false|Enable remote data source
 |**`rowsSelected`**|array||User provided selected rows
-|**`filterType `**|string|'dropdown'|Choice of filtering view. Options are "checkbox", "dropdown", or "multiselect"
+|**`filterType `**|string|'dropdown'|Choice of filtering view. Options are "checkbox", "dropdown", "multiselect" or "textField"
 |**`textLabels `**|object||User provided labels to localize text
 |**`pagination`**|boolean|true|Enable/disable pagination
 |**`selectableRows`**|boolean|true|Enable/disable row selection
diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js
index d40939e..3d8b237 100644
--- a/src/MUIDataTable.js
+++ b/src/MUIDataTable.js
@@ -75,7 +75,7 @@ class MUIDataTable extends React.Component {
     /** Options used to describe table */
     options: PropTypes.shape({
       responsive: PropTypes.oneOf(['stacked', 'scroll']),
-      filterType: PropTypes.oneOf(['dropdown', 'checkbox', 'multiselect']),
+      filterType: PropTypes.oneOf(['dropdown', 'checkbox', 'multiselect', 'textField']),
       textLabels: PropTypes.object,
       pagination: PropTypes.bool,
       expandableRows: PropTypes.bool,
@@ -435,24 +435,20 @@ class MUIDataTable extends React.Component {
 
       displayRow.push(columnDisplay);
 
-      if (filterList[index].length && filterList[index].indexOf(columnValue) < 0) {
-        isFiltered = true;
-      }
-
       const columnVal = columnValue === null ? '' : columnValue.toString();
 
-      if (searchText) {
-        let searchNeedle = searchText.toString();
-        let searchStack = columnVal.toString();
-
-        if (!this.options.caseSensitive) {
-          searchNeedle = searchNeedle.toLowerCase();
-          searchStack = searchStack.toLowerCase();
+      const filterVal = filterList[index];
+      const { filterType, caseSensitive } = this.options;
+      if (filterVal.length) {
+        if (filterType === 'textField' && !this.hasSearchText(columnVal, filterVal, caseSensitive)) {
+          isFiltered = true;
+        } else if (filterType !== 'textField' && filterVal.indexOf(columnValue) < 0) {
+          isFiltered = true;
         }
+      }
 
-        if (searchStack.indexOf(searchNeedle) >= 0) {
-          isSearchFound = true;
-        }
+      if (searchText && this.hasSearchText(columnVal, searchText, caseSensitive)) {
+        isSearchFound = true;
       }
     }
 
@@ -460,6 +456,18 @@ class MUIDataTable extends React.Component {
     else return displayRow;
   }
 
+  hasSearchText = (toSearch, toFind, caseSensitive) => {
+    let stack = toSearch.toString();
+    let needle = toFind.toString();
+
+    if (!caseSensitive) {
+      needle = needle.toLowerCase();
+      stack = stack.toLowerCase();
+    }
+
+    return stack.indexOf(needle) >= 0;
+  };
+
   updateDataCol = (row, index, value) => {
     this.setState(prevState => {
       let changedData = cloneDeep(prevState.data);
@@ -643,6 +651,7 @@ class MUIDataTable extends React.Component {
     this.setState(
       prevState => ({
         searchText: text && text.length ? text : null,
+        page: 0,
         displayData: this.options.serverSide
           ? prevState.displayData
           : this.getDisplayData(prevState.columns, prevState.data, prevState.filterList, text),
diff --git a/src/components/TableFilter.js b/src/components/TableFilter.js
index 3bf87c6..327ea86 100644
--- a/src/components/TableFilter.js
+++ b/src/components/TableFilter.js
@@ -12,6 +12,7 @@ import Select from '@material-ui/core/Select';
 import Checkbox from '@material-ui/core/Checkbox';
 import ListItemText from '@material-ui/core/ListItemText';
 import { withStyles } from '@material-ui/core/styles';
+import { TextField } from '@material-ui/core';
 
 export const defaultFilterStyles = {
   root: {
@@ -104,6 +105,19 @@ export const defaultFilterStyles = {
     marginRight: '24px',
     marginBottom: '24px',
   },
+  /* textField */
+  textFieldRoot: {
+    display: 'flex',
+    marginTop: '16px',
+    flexDirection: 'row',
+    flexWrap: 'wrap',
+    width: '100%',
+  },
+  textFieldFormControl: {
+    flex: '1 1 calc(50% - 24px)',
+    marginRight: '24px',
+    marginBottom: '24px',
+  },
 };
 
 class TableFilter extends React.Component {
@@ -135,6 +149,10 @@ class TableFilter extends React.Component {
     this.props.onFilterUpdate(index, column, 'multiselect');
   };
 
+  handleTextFieldChange = (event, index) => {
+    this.props.onFilterUpdate(index, event.target.value, 'textField');
+  };
+
   renderCheckbox(columns) {
     const { classes, filterData, filterList } = this.props;
 
@@ -208,6 +226,28 @@ class TableFilter extends React.Component {
     );
   }
 
+  renderTextField(columns) {
+    const { classes, filterList } = this.props;
+
+    return (
+      <div className={classes.textFieldRoot}>
+        {columns.map((column, index) =>
+          column.filter ? (
+            <FormControl className={classes.textFieldFormControl} key={index}>
+              <TextField
+                label={column.name}
+                value={filterList[index].toString() || ''}
+                onChange={event => this.handleTextFieldChange(event, index)}
+              />
+            </FormControl>
+          ) : (
+            false
+          ),
+        )}
+      </div>
+    );
+  }
+
   renderMultiselect(columns) {
     const { classes, filterData, filterList, options } = this.props;
 
@@ -274,6 +314,8 @@ class TableFilter extends React.Component {
           ? this.renderCheckbox(columns)
           : options.filterType === 'multiselect'
           ? this.renderMultiselect(columns)
+          : options.filterType === 'textField'
+          ? this.renderTextField(columns)
           : this.renderSelect(columns)}
       </div>
     );


Test patch:
diff --git a/test/MUIDataTable.test.js b/test/MUIDataTable.test.js
index a632e17..53b0d32 100644
--- a/test/MUIDataTable.test.js
+++ b/test/MUIDataTable.test.js
@@ -541,6 +541,38 @@ describe('<MUIDataTable />', function() {
     assert.strictEqual(options.onTableChange.callCount, 1);
   });
 
+  it('should render only things that match a filter', () => {
+    const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} />);
+    const table = shallowWrapper.dive();
+    const instance = table.instance();
+    instance.filterUpdate(0, 'James, Joe', 'checkbox');
+    table.update();
+    const state = table.state();
+
+    const expectedResult = JSON.stringify([
+      { data: ['James, Joe', 'Test Corp', renderCities('Yonkers', { rowIndex: 0 }), 'NY'], dataIndex: 0 },
+    ]);
+
+    assert.deepEqual(JSON.stringify(state.displayData), expectedResult);
+  });
+
+  it('should render all things that match a text field filter', () => {
+    const options = { filterType: 'textField' };
+    const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />);
+    const table = shallowWrapper.dive();
+    const instance = table.instance();
+    instance.filterUpdate(0, 'James', 'textField');
+    table.update();
+    const state = table.state();
+
+    const expectedResult = JSON.stringify([
+      { data: ['James, Joe', 'Test Corp', renderCities('Yonkers', { rowIndex: 0 }), 'NY'], dataIndex: 0 },
+      { data: ['Houston, James', 'Test Corp', renderCities('Dallas', { rowIndex: 3 }), 'TX'], dataIndex: 3 },
+    ]);
+
+    assert.deepEqual(JSON.stringify(state.displayData), expectedResult);
+  });
+
   describe('should correctly run comparator function', () => {
     it('correctly compares two equal strings', () => {
       expect(getCollatorComparator()('testString', 'testString')).to.equal(0);
diff --git a/test/MUIDataTableFilter.test.js b/test/MUIDataTableFilter.test.js
index 0e4bee2..b99df51 100644
--- a/test/MUIDataTableFilter.test.js
+++ b/test/MUIDataTableFilter.test.js
@@ -5,6 +5,7 @@ import { assert, expect, should } from 'chai';
 import textLabels from '../src/textLabels';
 import Select from '@material-ui/core/Select';
 import Checkbox from '@material-ui/core/Checkbox';
+import TextField from '@material-ui/core/TextField';
 import TableFilter from '../src/components/TableFilter';
 
 describe('<TableFilter />', function() {
@@ -96,6 +97,30 @@ describe('<TableFilter />', function() {
     assert.strictEqual(actualResult.length, 4);
   });
 
+  it("should data table filter view with TextFields if filterType = 'textfield'", () => {
+    const options = { filterType: 'textField', textLabels };
+    const filterList = [[], [], [], []];
+    const shallowWrapper = mount(
+      <TableFilter columns={columns} filterData={filterData} filterList={filterList} options={options} />,
+    );
+
+    const actualResult = shallowWrapper.find(TextField);
+    assert.strictEqual(actualResult.length, 4);
+  });
+
+  it("should data table filter view with no TextFields if filter=false when filterType = 'textField'", () => {
+    const options = { filterType: 'textField', textLabels };
+    const filterList = [[], [], [], []];
+    columns = columns.map(item => (item.filter = false));
+
+    const shallowWrapper = mount(
+      <TableFilter columns={columns} filterData={filterData} filterList={filterList} options={options} />,
+    );
+
+    const actualResult = shallowWrapper.find(TextField);
+    assert.strictEqual(actualResult.length, 0);
+  });
+
   it('should trigger onFilterUpdate prop callback when calling method handleCheckboxChange', () => {
     const options = { filterType: 'checkbox', textLabels };
     const filterList = [[], [], [], []];