Repository: gregnb/mui-datatables (js)

Issue:
Customize row theme based on data
Hi,
I already use the customization of the theme like this:
`var theme = createMuiTheme({
      overrides: {
        MUIDataTableBodyCell: {
          root: {
            fontSize: ".9rem",
            fontWeight: "bold",
          }
        },
        MUIDataTableBodyRow: {
          root: {
          }
        }
      },
    });`

but now I need to customize the row based on content like : column "date_end" is different from "" so backgroundColor: red.. I have found how do it possible based on the index but not we the data. Someone can help me? Thanks

Pull request description:
add custom styling methods for rows and cells
fixes #304 

This adds 2 functions
On table options:
`getRowClassName`
 which is called for each row and adds classes returned from it to the `MUIDataTableBodyRow`  object .
Allows styling of each row based on its data

And `getRowClassName` on column options, which does the same for cells


Patch:
diff --git a/README.md b/README.md
index 05b1846..31af8af 100644
--- a/README.md
+++ b/README.md
@@ -167,6 +167,7 @@ The component accepts the following props:
 |**`onColumnSortChange`**|function||Callback function that triggers when a column has been sorted. `function(changedColumn: string, direction: string) => void`
 |**`onColumnViewChange`**|function||Callback function that triggers when a column view has been changed. `function(changedColumn: string, action: string) => void`
 |**`onTableChange`**|function||Callback function that triggers when table state has changed. `function(action: string, tableState: object) => void`
+|**`setRowProps`**|function||Is called for each row and allows to return custom props for this row based on its data. `function(row: array, rowIndex: number) => object`
 
 ## Customize Columns
 
@@ -200,8 +201,9 @@ const columns = [
 |**`sort`**|boolean|true|Enable/disable sorting on column
 |**`download`**|boolean|true|Display column in CSV download file
 |**`hint`**|string|`undefined`Display hint icon with string as tooltip on hover.
-|**`customHeadRender`**|function||Function that returns a string or React component. Used as display for column header. `function(value, tableMeta, updateValue) => string`&#124;`
+|**`customHeadRender`**|function||Function that returns a string or React component. Used as display for column header. `function(value, tableMeta, updateValue) => string`&#124;
 |**`customBodyRender`**|function||Function that returns a string or React component. Used as display data within all table cells of a given column. `function(value, tableMeta, updateValue) => string`&#124;` React Component` [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/component/index.js)
+|**`setCellProps`**|function||Is called for each cell and allows to return custom props for this cell based on its data. `function(cellValue: string, rowIndex: number, columnIndex: number) => string`
 
 `customHeadRender` is called with these arguments:
 
diff --git a/examples/customize-styling/index.js b/examples/customize-styling/index.js
index 89246ec..e849100 100644
--- a/examples/customize-styling/index.js
+++ b/examples/customize-styling/index.js
@@ -1,7 +1,17 @@
 import React from "react";
 import ReactDOM from "react-dom";
 import MUIDataTable from "../../src/";
-import { createMuiTheme, MuiThemeProvider, withStyles } from '@material-ui/core/styles';
+import {createMuiTheme, MuiThemeProvider, withStyles} from '@material-ui/core/styles';
+import classnames from 'classnames';
+
+const customStyles = {
+  BusinessAnalystRow: {
+    '& td': {backgroundColor: "#F00"}
+  },
+  NameCell: {
+    fontWeight: 900
+  },
+};
 
 class Example extends React.Component {
 
@@ -21,17 +31,24 @@ class Example extends React.Component {
         }
       }
     }
-  })
+  });
 
   render() {
-
     const columns = [
       {
         name: "Name",
         options: {
           filter: true,
+          setCellProps: (value) => {
+            return {
+              className: classnames(
+                {
+                  [this.props.classes.NameCell]: value === "Mel Brooks"
+                })
+            };
+          }
         }
-      },      
+      },
       {
         name: "Title",
         options: {
@@ -56,12 +73,12 @@ class Example extends React.Component {
           filter: true,
           sort: false
         }
-      }      
+      }
     ];
 
     const data = [
       ["Gabby George", "Business Analyst", "Minneapolis", 30, 100000],
-      ["Aiden Lloyd", "Business Consultant", "Dallas",  55, 200000],
+      ["Aiden Lloyd", "Business Consultant", "Dallas", 55, 200000],
       ["Jaden Collins", "Attorney", "Santa Ana", 27, 500000],
       ["Franky Rees", "Business Analyst", "St. Petersburg", 22, 50000],
       ["Aaren Rose", "Business Consultant", "Toledo", 28, 75000],
@@ -96,15 +113,27 @@ class Example extends React.Component {
       filter: true,
       filterType: 'dropdown',
       responsive: 'stacked',
+      setRowProps: (row) => {
+        return {
+          className: classnames(
+            {
+              [this.props.classes.BusinessAnalystRow]: row[1] === "Business Analyst"
+            }),
+          style: {border: '3px solid blue',}
+        };
+      }
+
     };
 
     return (
       <MuiThemeProvider theme={this.getMuiTheme()}>
-        <MUIDataTable title={"ACME Employee list"} data={data} columns={columns} options={options} />
+        <MUIDataTable title={"ACME Employee list"} data={data} columns={columns} options={options}/>
       </MuiThemeProvider>
     );
 
   }
 }
 
-ReactDOM.render(<Example />, document.getElementById("app-root"));
+const ExampleWithStyles = withStyles(customStyles, {name: "Example"})(Example);
+
+ReactDOM.render(<ExampleWithStyles/>, document.getElementById("app-root"));
diff --git a/src/MUIDataTableBody.js b/src/MUIDataTableBody.js
index 6526512..fb46da5 100644
--- a/src/MUIDataTableBody.js
+++ b/src/MUIDataTableBody.js
@@ -89,17 +89,18 @@ class MUIDataTableBody extends React.Component {
     const { classes, columns, options } = this.props;
     const tableRows = this.buildRows();
 
-    return (
-      <TableBody>
-        {tableRows ? (
-          tableRows.map(({ data: row, dataIndex }, rowIndex) => (
+    if (tableRows) {
+      return (
+        <TableBody>
+          {tableRows.map(({ data: row, dataIndex }, rowIndex) => (
             <MUIDataTableBodyRow
+              {...(options.setRowProps ? options.setRowProps(row,dataIndex) : {})}
               options={options}
               rowSelected={options.selectableRows ? this.isRowSelected(dataIndex) : false}
               onClick={options.onRowClick ? options.onRowClick.bind(null, row, { rowIndex, dataIndex }) : null}
               id={"MUIDataTableBodyRow-" + dataIndex}
               key={rowIndex}>
-              {options.selectableRows ? (
+              {options.selectableRows && (
                 <MUIDataTableSelectCell
                   onChange={this.handleRowSelect.bind(null, {
                     index: this.getRowIndex(rowIndex),
@@ -108,27 +109,31 @@ class MUIDataTableBody extends React.Component {
                   fixedHeader={options.fixedHeader}
                   checked={this.isRowSelected(dataIndex)}
                 />
-              ) : (
-                false
               )}
-              {row.map((column, index) =>
-                columns[index].display === "true" ? (
-                  <MUIDataTableBodyCell
-                    dataIndex={dataIndex}
-                    rowIndex={rowIndex}
-                    colIndex={index}
-                    columnHeader={columns[index].name}
-                    options={options}
-                    key={index}>
-                    {column}
-                  </MUIDataTableBodyCell>
-                ) : (
-                  false
-                ),
+              {row.map(
+                (column, columnIndex) =>
+                  columns[columnIndex].display === "true" && (
+                    <MUIDataTableBodyCell
+                      {...(columns[columnIndex].setCellProps ? columns[columnIndex].setCellProps(column, dataIndex, columnIndex) : {})}
+                      dataIndex={dataIndex}
+                      rowIndex={rowIndex}
+                      colIndex={columnIndex}
+                      columnHeader={columns[columnIndex].name}
+                      options={options}
+                      key={columnIndex}>
+                      {column}
+                    </MUIDataTableBodyCell>
+                  ),
               )}
             </MUIDataTableBodyRow>
-          ))
-        ) : (
+          ))}
+        </TableBody>
+      );
+    }
+
+    return (
+      <TableBody>
+        {
           <MUIDataTableBodyRow options={options}>
             <MUIDataTableBodyCell
               colSpan={options.selectableRows ? columns.length + 1 : columns.length}
@@ -140,7 +145,7 @@ class MUIDataTableBody extends React.Component {
               </Typography>
             </MUIDataTableBodyCell>
           </MUIDataTableBodyRow>
-        )}
+        }
       </TableBody>
     );
   }
diff --git a/src/MUIDataTableBodyCell.js b/src/MUIDataTableBodyCell.js
index b9b1290..31d709c 100644
--- a/src/MUIDataTableBodyCell.js
+++ b/src/MUIDataTableBodyCell.js
@@ -38,25 +38,41 @@ class MUIDataTableBodyCell extends React.Component {
   };
 
   render() {
-    const { children, classes, colIndex, columnHeader, options, dataIndex, rowIndex, ...otherProps } = this.props;
+    const {
+      children,
+      classes,
+      colIndex,
+      columnHeader,
+      options,
+      dataIndex,
+      rowIndex,
+      className,
+      ...otherProps
+    } = this.props;
 
     return [
       <TableCell
         key={1}
-        className={classNames({
-          [classes.root]: true,
-          [classes.cellHide]: true,
-          [classes.cellStacked]: options.responsive === "stacked",
-        })}>
+        className={classNames(
+          {
+            [classes.root]: true,
+            [classes.cellHide]: true,
+            [classes.cellStacked]: options.responsive === "stacked",
+          },
+          className,
+        )}>
         {columnHeader}
       </TableCell>,
       <TableCell
         key={2}
         onClick={this.handleClick}
-        className={classNames({
-          [classes.root]: true,
-          [classes.responsiveStacked]: options.responsive === "stacked",
-        })}
+        className={classNames(
+          {
+            [classes.root]: true,
+            [classes.responsiveStacked]: options.responsive === "stacked",
+          },
+          className,
+        )}
         {...otherProps}>
         {children}
       </TableCell>,
diff --git a/src/MUIDataTableBodyRow.js b/src/MUIDataTableBodyRow.js
index d6bedc3..d9d8878 100644
--- a/src/MUIDataTableBodyRow.js
+++ b/src/MUIDataTableBodyRow.js
@@ -26,17 +26,21 @@ class MUIDataTableBodyRow extends React.Component {
   };
 
   render() {
-    const { classes, options, rowSelected, onClick } = this.props;
+    const { classes, options, rowSelected, onClick, className, ...rest } = this.props;
 
     return (
       <TableRow
         hover={options.rowHover ? true : false}
         onClick={onClick}
-        className={classNames({
-          [classes.root]: true,
-          [classes.responsiveStacked]: options.responsive === "stacked",
-        })}
-        selected={rowSelected}>
+        className={classNames(
+          {
+            [classes.root]: true,
+            [classes.responsiveStacked]: options.responsive === "stacked",
+          },
+          className,
+        )}
+        selected={rowSelected}
+        {...rest}>
         {this.props.children}
       </TableRow>
     );
diff --git a/webpack.config.js b/webpack.config.js
index f3cd389..b24d898 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,7 +3,7 @@ const webpack = require('webpack');
 
 module.exports = {
   entry: {
-    app: "./examples/selectable-rows/index.js"
+    app: "./examples/customize-styling/index.js"
   },
   stats: "verbose",
   context: __dirname,


Test patch:
diff --git a/test/MUIDataTableBody.test.js b/test/MUIDataTableBody.test.js
index e42ad5b..88a1953 100644
--- a/test/MUIDataTableBody.test.js
+++ b/test/MUIDataTableBody.test.js
@@ -1,18 +1,23 @@
 import React from "react";
-import { spy, stub } from "sinon";
-import { mount, shallow } from "enzyme";
-import { assert, expect, should } from "chai";
+import {spy, stub} from "sinon";
+import {mount, shallow} from "enzyme";
+import {assert, expect, should} from "chai";
 import textLabels from "../src/textLabels";
 import MUIDataTableBody from "../src/MUIDataTableBody";
 import MUIDataTableSelectCell from "../src/MUIDataTableSelectCell";
 
-describe("<MUIDataTableBody />", function() {
+describe("<MUIDataTableBody />", function () {
   let data;
   let displayData;
   let columns;
 
   before(() => {
-    columns = ["First Name", "Company", "City", "State"];
+    columns = [
+      {name: "First Name"},
+      {name: "Company"},
+      {name: "City"},
+      {name: "State"},
+    ];
     data = [
       ["Joe James", "Test Corp", "Yonkers", "NY"],
       ["John Walsh", "Test Corp", "Hartford", "CT"],
@@ -40,8 +45,9 @@ describe("<MUIDataTableBody />", function() {
   });
 
   it("should render a table body with no selectable cells if selectableRows = false", () => {
-    const options = { selectableRows: false };
-    const selectRowUpdate = () => {};
+    const options = {selectableRows: false};
+    const selectRowUpdate = () => {
+    };
 
     const mountWrapper = mount(
       <MUIDataTableBody
@@ -63,8 +69,9 @@ describe("<MUIDataTableBody />", function() {
   });
 
   it("should render a table body with no records if no data provided", () => {
-    const options = { selectableRows: false, textLabels };
-    const selectRowUpdate = () => {};
+    const options = {selectableRows: false, textLabels};
+    const selectRowUpdate = () => {
+    };
 
     const mountWrapper = mount(
       <MUIDataTableBody
@@ -85,8 +92,9 @@ describe("<MUIDataTableBody />", function() {
   });
 
   it("should render a table body with selectable cells if selectableRows = true", () => {
-    const options = { selectableRows: true };
-    const selectRowUpdate = () => {};
+    const options = {selectableRows: true};
+    const selectRowUpdate = () => {
+    };
 
     const mountWrapper = mount(
       <MUIDataTableBody
@@ -108,8 +116,9 @@ describe("<MUIDataTableBody />", function() {
   });
 
   it("should return the correct rowIndex when calling instance method getRowIndex", () => {
-    const options = { sort: true, selectableRows: true };
-    const selectRowUpdate = () => {};
+    const options = {sort: true, selectableRows: true};
+    const selectRowUpdate = () => {
+    };
 
     const shallowWrapper = shallow(
       <MUIDataTableBody
@@ -133,8 +142,9 @@ describe("<MUIDataTableBody />", function() {
   });
 
   it("should return correctly if row exists in selectedRows when calling instance method isRowSelected", () => {
-    const options = { sort: true, selectableRows: true };
-    const selectRowUpdate = () => {};
+    const options = {sort: true, selectableRows: true};
+    const selectRowUpdate = () => {
+    };
 
     const shallowWrapper = shallow(
       <MUIDataTableBody
@@ -158,7 +168,7 @@ describe("<MUIDataTableBody />", function() {
   });
 
   it("should trigger selectRowUpdate prop callback when calling method handleRowSelect", () => {
-    const options = { sort: true, selectableRows: true };
+    const options = {sort: true, selectableRows: true};
     const selectRowUpdate = spy();
 
     const shallowWrapper = shallow(
@@ -184,7 +194,7 @@ describe("<MUIDataTableBody />", function() {
   });
 
   it("should call onRowClick when Row is clicked", () => {
-    const options = { selectableRows: true, onRowClick: spy() };
+    const options = {selectableRows: true, onRowClick: spy()};
     const selectRowUpdate = stub();
 
     const t = mount(
@@ -203,10 +213,38 @@ describe("<MUIDataTableBody />", function() {
     );
 
     t.find("#MUIDataTableBodyRow-2")
-      .first()
-      .simulate("click");
+     .first()
+     .simulate("click");
 
     assert.strictEqual(options.onRowClick.callCount, 1);
-    assert(options.onRowClick.calledWith(data[2], { rowIndex: 2, dataIndex: 2 }));
+    assert(options.onRowClick.calledWith(data[2], {rowIndex: 2, dataIndex: 2}));
+  });
+
+  it("should add custom props to rows if 'setRowProps' provided", () => {
+    const options = {setRowProps: stub().returns({className: "testClass"})};
+    const selectRowUpdate = stub();
+
+    const t = mount(
+      <MUIDataTableBody
+        data={displayData}
+        count={displayData.length}
+        columns={columns}
+        page={0}
+        rowsPerPage={10}
+        selectedRows={[]}
+        selectRowUpdate={selectRowUpdate}
+        options={options}
+        searchText={""}
+        filterList={[]}
+      />,
+    );
+
+    const props = t.find("#MUIDataTableBodyRow-1")
+                   .first()
+                   .props();
+
+    assert.strictEqual(props.className, "testClass");
+    assert.isAtLeast(options.setRowProps.callCount, 1);
+    assert(options.setRowProps.calledWith(data[1]));
   });
 });