Repository: gregnb/mui-datatables
Pull request #82 (2018-06-10 20:48:46)

add tableMeta to customRender function
When merged this should address #63 

@jkantr and @samsch would love some feedback on this PR. 

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

Coverage increased (+0.09%) to 84.97% when pulling **5ef677586eaea8ece5c20e5865bc14ec7862a7bb on feature/additional-meta-customrender** into **54bf78607876450c4603675862e2119c731f6727 on master**.

gregnb: TODO: Update README to reflect changes
samsch: I had a little time to put together at least part of what's needed for the docs. Probably isn't formatted as you'll want, but might save you a bit of work: https://github.com/gregnb/mui-datatables/commit/e19fb3610e8504d5c487769e7eb3953ae80cb3d7

Patch:
diff --git a/.eslintrc b/.eslintrc
index caa7a10..7372fa1 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -33,7 +33,11 @@
   "rules": {
     "no-console": "off",
     "semi": 2,
+    "no-undef": 2,
+    "no-undef-init": 2,
     "no-tabs": 2,
+    "react/self-closing-comp": 2,
+    "react/no-typos": 2,
     "jsx-a11y/no-autofocus": [ 2, {
       "ignoreNonDOM": true
     }]
diff --git a/README.md b/README.md
index d472b83..f1722cc 100644
--- a/README.md
+++ b/README.md
@@ -270,3 +270,10 @@ const options = {
 
 ## License
 The files included in this repository are licensed under the MIT license.
+
+
+## Thanks
+
+[<img src="https://www.browserstack.com/images/mail/browserstack-logo-footer.png" width="120">](https://www.browserstack.com/)
+
+Thank you to [BrowserStack](https://www.browserstack.com/) for providing the infrastructure that allows us to test in real browsers.
\ No newline at end of file
diff --git a/examples/component/cities.js b/examples/component/cities.js
index 6c35ce2..ab6cc4e 100644
--- a/examples/component/cities.js
+++ b/examples/component/cities.js
@@ -2,7 +2,7 @@ import React from "react";
 import PropTypes from 'prop-types';
 import MenuItem from '@material-ui/core/MenuItem';
 import FormControl from '@material-ui/core/FormControl';
-import Select from '@material-ui/core/Select'
+import Select from '@material-ui/core/Select';
 
 class Cities extends React.Component {
   static propTypes = {
diff --git a/examples/component/index.js b/examples/component/index.js
index b260c7e..8c24e3e 100644
--- a/examples/component/index.js
+++ b/examples/component/index.js
@@ -1,7 +1,7 @@
 import React from "react";
 import ReactDOM from "react-dom";
-import { FormControlLabel } from 'material-ui/Form';
-import Switch from 'material-ui/Switch';
+import FormControlLabel from '@material-ui/core/FormControlLabel';
+import Switch from '@material-ui/core/Switch';
 import MUIDataTable from "../../src/";
 import Cities from "./cities";
 
@@ -26,11 +26,11 @@ class Example extends React.Component {
         name: "Location",
         options: {
           filter: true,
-          customRender: (index, value, updateValue) => {            
+          customRender: (value, tableMeta, updateValue) => {
             return (
               <Cities
                 value={value}
-                index={index}
+                index={tableMeta.columnIndex}
                 change={event => updateValue(event)}
               />
             );
@@ -47,7 +47,7 @@ class Example extends React.Component {
         name: "Salary",
         options: {
           filter: true,
-          customRender: (index, value) => {
+          customRender: (value, tableMeta, updateValue) => {
 
             const nf = new Intl.NumberFormat('en-US', {
               style: 'currency',
@@ -64,18 +64,22 @@ class Example extends React.Component {
         name: "Active",
         options: {
           filter: true,
-          customRender: (index, value, updateValue) => (
-            <FormControlLabel
-              label={value ? "Yes" : "No"}
-              value={value ? "Yes" : "No"}
-              control={
-                <Switch color="primary" checked={value} value={value ? "Yes" : "No"} />
-              }
-              onChange={event => {
-                updateValue(event.target.value === "Yes" ? false : true);
-              }}
-            />
-          )
+          customRender: (value, tableMeta, updateValue) => {
+
+            return (
+              <FormControlLabel
+                label={value ? "Yes" : "No"}
+                value={value ? "Yes" : "No"}
+                control={
+                  <Switch color="primary" checked={value} value={value ? "Yes" : "No"} />
+                }
+                onChange={event => {
+                  updateValue(event.target.value === "Yes" ? false : true);
+                }}
+              />
+            );
+
+          }
         }
       }
     ];
diff --git a/package-lock.json b/package-lock.json
index 6241f81..9767346 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "mui-datatables",
-  "version": "2.0.0-beta-7",
+  "version": "2.0.0-beta-8",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -3527,6 +3527,19 @@
         "resolve": "1.7.1"
       }
     },
+    "eslint-loader": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.0.0.tgz",
+      "integrity": "sha512-VxxGDI4bXzLk0+/jMt/0EkGMRKS9ox6Czx+yapMb9WJmcS/ZHhlhqcVUNgUjFBNp02j/2pZLdGOrG7EXyjoz/g==",
+      "dev": true,
+      "requires": {
+        "loader-fs-cache": "1.0.1",
+        "loader-utils": "1.1.0",
+        "object-assign": "4.1.1",
+        "object-hash": "1.3.0",
+        "rimraf": "2.6.2"
+      }
+    },
     "eslint-module-utils": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz",
@@ -6316,6 +6329,57 @@
         }
       }
     },
+    "loader-fs-cache": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz",
+      "integrity": "sha1-VuC/CL2XCLJqdltoUJhAyN7J/bw=",
+      "dev": true,
+      "requires": {
+        "find-cache-dir": "0.1.1",
+        "mkdirp": "0.5.1"
+      },
+      "dependencies": {
+        "find-cache-dir": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz",
+          "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=",
+          "dev": true,
+          "requires": {
+            "commondir": "1.0.1",
+            "mkdirp": "0.5.1",
+            "pkg-dir": "1.0.0"
+          }
+        },
+        "find-up": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+          "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+          "dev": true,
+          "requires": {
+            "path-exists": "2.1.0",
+            "pinkie-promise": "2.0.1"
+          }
+        },
+        "path-exists": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+          "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+          "dev": true,
+          "requires": {
+            "pinkie-promise": "2.0.1"
+          }
+        },
+        "pkg-dir": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
+          "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
+          "dev": true,
+          "requires": {
+            "find-up": "1.1.2"
+          }
+        }
+      }
+    },
     "loader-runner": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz",
@@ -10092,6 +10156,12 @@
         }
       }
     },
+    "object-hash": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.0.tgz",
+      "integrity": "sha512-05KzQ70lSeGSrZJQXE5wNDiTkBJDlUT/myi6RX9dVIvz7a7Qh4oH93BQdiPMn27nldYvVQCKMUaM83AfizZlsQ==",
+      "dev": true
+    },
     "object-inspect": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz",
diff --git a/package.json b/package.json
index e1387f6..bcd9a68 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
     "enzyme": "^3.2.0",
     "enzyme-adapter-react-16": "^1.1.0",
     "eslint": "^4.14.0",
+    "eslint-loader": "^2.0.0",
     "eslint-plugin-filenames": "^1.2.0",
     "eslint-plugin-import": "^2.8.0",
     "eslint-plugin-jsx-a11y": "^6.0.3",
diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js
index f27d09b..db34af1 100644
--- a/src/MUIDataTable.js
+++ b/src/MUIDataTable.js
@@ -94,7 +94,6 @@ class MUIDataTable extends React.Component {
   };
 
   state = {
-    open: false,
     announceText: null,
     data: [],
     displayData: [],
@@ -209,7 +208,7 @@ class MUIDataTable extends React.Component {
       for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
         let value = status === TABLE_LOAD.INITIAL ? data[rowIndex][colIndex] : data[rowIndex].data[colIndex];
 
-        if (tableData[rowIndex] === undefined) {
+        if (typeof tableData[rowIndex] === "undefined") {
           tableData.push({
             index: status === TABLE_LOAD.INITIAL ? rowIndex : data[rowIndex].index,
             data: status === TABLE_LOAD.INITIAL ? data[rowIndex] : data[rowIndex].data,
@@ -217,7 +216,8 @@ class MUIDataTable extends React.Component {
         }
 
         if (typeof columnOptions.customRender === "function") {
-          const funcResult = columnOptions.customRender(rowIndex, value);
+          const tableMeta = this.getCustomRenderMeta(rowIndex, colIndex, value, [], columnData, this.state);
+          const funcResult = columnOptions.customRender(value, tableMeta);
 
           if (React.isValidElement(funcResult) && funcResult.props.value) {
             value = funcResult.props.value;
@@ -269,39 +269,53 @@ class MUIDataTable extends React.Component {
   /*
    *  Build the table data used to display to the user (ie: after filter/search applied)
    */
-
-  isRowDisplayed(columns, row, filterList, searchText) {
-    let isFiltered = false,
-      isSearchFound = false;
+  computeDisplayRow(columns, row, rowIndex, filterList, searchText) {
+    let isFiltered = false;
+    let isSearchFound = false;
+    let displayRow = [];
 
     for (let index = 0; index < row.length; index++) {
-      let column = row[index];
+      let columnDisplay = row[index];
+      let columnValue = row[index];
 
       if (columns[index].customRender) {
-        const funcResult = columns[index].customRender(index, column);
-        column =
+        const tableMeta = this.getCustomRenderMeta(rowIndex, index, row, columns[index], this.state.data, {
+          ...this.state,
+          filterList: filterList,
+          searchText: searchText,
+        });
+
+        const funcResult = columns[index].customRender(
+          columnValue,
+          tableMeta,
+          this.updateDataCol.bind(null, rowIndex, index),
+        );
+        columnDisplay = funcResult;
+
+        /* drill down to get the value of a cell */
+        columnValue =
           typeof funcResult === "string"
             ? funcResult
             : funcResult.props && funcResult.props.value
               ? funcResult.props.value
-              : column;
+              : columnValue;
       }
 
-      if (filterList[index].length && filterList[index].indexOf(column) < 0) {
+      displayRow.push(columnDisplay);
+
+      if (filterList[index].length && filterList[index].indexOf(columnValue) < 0) {
         isFiltered = true;
-        break;
       }
 
-      const searchCase = !this.options.caseSensitive ? column.toString().toLowerCase() : column.toString();
+      const searchCase = !this.options.caseSensitive ? columnValue.toString().toLowerCase() : columnValue.toString();
 
       if (searchText && searchCase.indexOf(searchText.toLowerCase()) >= 0) {
         isSearchFound = true;
-        break;
       }
     }
 
-    if (isFiltered || (searchText && !isSearchFound)) return false;
-    else return true;
+    if (isFiltered || (searchText && !isSearchFound)) return null;
+    else return displayRow;
   }
 
   updateDataCol = (row, index, value) => {
@@ -309,7 +323,8 @@ class MUIDataTable extends React.Component {
       let changedData = cloneDeep(prevState.data);
       let filterData = cloneDeep(prevState.filterData);
 
-      const funcResult = prevState.columns[index].customRender(index, value);
+      const tableMeta = this.getCustomRenderMeta(row, index, row, prevState.columns[index], prevState.data, prevState);
+      const funcResult = prevState.columns[index].customRender(value, tableMeta);
 
       const filterValue =
         React.isValidElement(funcResult) && funcResult.props.value
@@ -319,7 +334,7 @@ class MUIDataTable extends React.Component {
       const prevFilterIndex = filterData[index].indexOf(filterValue);
       filterData[index].splice(prevFilterIndex, 1, filterValue);
 
-      changedData[row][index] = value;
+      changedData[row].data[index] = value;
 
       if (this.options.sortFilterList) {
         const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
@@ -334,20 +349,29 @@ class MUIDataTable extends React.Component {
     });
   };
 
+  getCustomRenderMeta = (rowIndex, colIndex, rowData, columnData, tableData, curState) => {
+    const { columns, data, displayData, filterData, ...tableState } = curState;
+
+    return {
+      rowIndex: rowIndex,
+      columnIndex: colIndex,
+      columnData: columnData,
+      rowData: rowData,
+      tableData: tableData,
+      tableState: tableState,
+    };
+  };
+
   getDisplayData(columns, data, filterList, searchText) {
     let newRows = [];
 
     for (let index = 0; index < data.length; index++) {
       const value = data[index].data;
-      if (this.isRowDisplayed(columns, value, filterList, searchText)) {
-        const columnData = columns.map((column, colIndex) => {
-          return typeof column.customRender === "function"
-            ? column.customRender(index, value[colIndex], this.updateDataCol.bind(null, index, colIndex))
-            : value[colIndex];
-        });
+      const displayRow = this.computeDisplayRow(columns, value, index, filterList, searchText);
 
+      if (displayRow) {
         newRows.push({
-          data: columnData,
+          data: displayRow,
           dataIndex: data[index].index,
         });
       }
@@ -607,9 +631,6 @@ class MUIDataTable extends React.Component {
       (order === "asc" ? -1 : 1);
   }
 
-  //
-  // revisit if this can be optimzed
-  //
   sortTable(data, col, order) {
     let sortedData = data.map((row, sIndex) => ({
       data: row.data[col],
diff --git a/webpack.config.js b/webpack.config.js
index 71708aa..b176c22 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,7 +3,7 @@ const webpack = require('webpack');
 
 module.exports = {
   entry: {
-    app: "./examples/customize-toolbarselect/index.js"
+    app: "./examples/component/index.js"
   },
   stats: "verbose",
   context: __dirname,
@@ -23,7 +23,10 @@ module.exports = {
       {
         test: /\.jsx?$/,
         exclude: /(node_modules)/,
-        use: 'babel-loader'
+        use: [
+          'babel-loader',
+          'eslint-loader'
+        ]
       }
     ]
   },


Test patch:
diff --git a/test/MUIDataTable.test.js b/test/MUIDataTable.test.js
index c742e68..6be507a 100644
--- a/test/MUIDataTable.test.js
+++ b/test/MUIDataTable.test.js
@@ -14,10 +14,10 @@ describe("<MUIDataTable />", function() {
   let displayData;
   let columns;
   let tableData;
-  let renderCities = (index, value, updateValue) => (
-    <Cities value={value} index={index} change={event => updateValue(event)} />
+  let renderCities = (value, tableMeta, updateValueFn) => (
+    <Cities value={value} index={tableMeta.rowIndex} change={event => updateValueFn(event)} />
   );
-  let renderName = (index, value) => value.split(" ")[1] + ", " + value.split(" ")[0];
+  let renderName = value => value.split(" ")[1] + ", " + value.split(" ")[0];
 
   before(() => {
     columns = [
@@ -35,27 +35,27 @@ describe("<MUIDataTable />", function() {
     // internal table data built from source data provided
     displayData = JSON.stringify([
       {
-        data: ["James, Joe", "Test Corp", renderCities(0, "Yonkers"), "NY"],
+        data: ["James, Joe", "Test Corp", renderCities("Yonkers", { rowIndex: 0 }), "NY"],
         dataIndex: 0,
       },
       {
-        data: ["Walsh, John", "Test Corp", renderCities(1, "Hartford"), "CT"],
+        data: ["Walsh, John", "Test Corp", renderCities("Hartford", { rowIndex: 1 }), "CT"],
         dataIndex: 1,
       },
       {
-        data: ["Herm, Bob", "Test Corp", renderCities(2, "Tampa"), "FL"],
+        data: ["Herm, Bob", "Test Corp", renderCities("Tampa", { rowIndex: 2 }), "FL"],
         dataIndex: 2,
       },
       {
-        data: ["Houston, James", "Test Corp", renderCities(3, "Dallas"), "TX"],
+        data: ["Houston, James", "Test Corp", renderCities("Dallas", { rowIndex: 3 }), "TX"],
         dataIndex: 3,
       },
     ]);
     tableData = [
-      { index: 0, data: ["James, Joe", "Test Corp", renderCities(0, "Yonkers"), "NY"] },
-      { index: 1, data: ["Walsh, John", "Test Corp", renderCities(1, "Hartford"), "CT"] },
-      { index: 2, data: ["Herm, Bob", "Test Corp", renderCities(2, "Tampa"), "FL"] },
-      { index: 3, data: ["Houston, James", "Test Corp", renderCities(3, "Dallas"), "TX"] },
+      { index: 0, data: ["James, Joe", "Test Corp", renderCities("Yonkers", { rowIndex: 0 }), "NY"] },
+      { index: 1, data: ["Walsh, John", "Test Corp", renderCities("Hartford", { rowIndex: 1 }), "CT"] },
+      { index: 2, data: ["Herm, Bob", "Test Corp", renderCities("Tampa", { rowIndex: 2 }), "FL"] },
+      { index: 3, data: ["Houston, James", "Test Corp", renderCities("Dallas", { rowIndex: 3 }), "TX"] },
     ];
     renderCities = renderCities;
     renderName = renderName;
@@ -294,7 +294,7 @@ describe("<MUIDataTable />", function() {
     const state = table.state();
 
     const expectedResult = JSON.stringify([
-      { data: ["James, Joe", "Test Corp", renderCities(0, "Yonkers"), "NY"], dataIndex: 0 },
+      { data: ["James, Joe", "Test Corp", renderCities("Yonkers", { rowIndex: 0 }), "NY"], dataIndex: 0 },
     ]);
 
     assert.deepEqual(JSON.stringify(state.displayData), expectedResult);
@@ -309,10 +309,10 @@ describe("<MUIDataTable />", function() {
     const state = shallowWrapper.state();
 
     const expectedResult = JSON.stringify([
-      { data: ["Herm, Bob", "Test Corp", renderCities(0, "Tampa"), "FL"], dataIndex: 2 },
-      { data: ["Houston, James", "Test Corp", renderCities(1, "Dallas"), "TX"], dataIndex: 3 },
-      { data: ["James, Joe", "Test Corp", renderCities(2, "Yonkers"), "NY"], dataIndex: 0 },
-      { data: ["Walsh, John", "Test Corp", renderCities(3, "Hartford"), "CT"], dataIndex: 1 },
+      { data: ["Herm, Bob", "Test Corp", renderCities("Tampa", { rowIndex: 0 }), "FL"], dataIndex: 2 },
+      { data: ["Houston, James", "Test Corp", renderCities("Dallas", { rowIndex: 1 }), "TX"], dataIndex: 3 },
+      { data: ["James, Joe", "Test Corp", renderCities("Yonkers", { rowIndex: 2 }), "NY"], dataIndex: 0 },
+      { data: ["Walsh, John", "Test Corp", renderCities("Hartford", { rowIndex: 3 }), "CT"], dataIndex: 1 },
     ]);
 
     assert.deepEqual(JSON.stringify(state.displayData), expectedResult);
@@ -405,6 +405,6 @@ describe("<MUIDataTable />", function() {
     shallowWrapper.update();
 
     const state = shallowWrapper.state();
-    assert.deepEqual(state.data[0][2], "Las Vegas");
+    assert.deepEqual(state.data[0].data[2], "Las Vegas");
   });
 });