Skip to content

Commit

Permalink
Merge pull request #1339 from gregnb/customBodyRenderLite
Browse files Browse the repository at this point in the history
Added customBodyRenderLite / removed object deprecation for column values
  • Loading branch information
patorjk authored Jun 18, 2020
2 parents 5e9583f + 09244db commit 77170f7
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 51 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ The component accepts the following props:
|:--:|:-----|:-----|
|**`title`**|array|Title used to caption table
|**`columns`**|array|Columns used to describe table. Must be either an array of simple strings or objects describing a column
|**`data`**|array|Data used to describe table. Must be either an array containing objects of key/value pairs with values that are strings or numbers, or arrays of strings or numbers (Ex: data: [{"Name": "Joe", "Job Title": "Plumber", "Age": 30}, {"Name": "Jane", "Job Title": "Electrician", "Age": 45}] or data: [["Joe", "Plumber", 30], ["Jane", "Electrician", 45]])
|**`data`**|array|Data used to describe table. Must be either an array containing objects of key/value pairs with values that are strings or numbers, or arrays of strings or numbers (Ex: data: [{"Name": "Joe", "Job Title": "Plumber", "Age": 30}, {"Name": "Jane", "Job Title": "Electrician", "Age": 45}] or data: [["Joe", "Plumber", 30], ["Jane", "Electrician", 45]]). The **customBodyRender** and **customBodyRenderLite** options can be used to control the data diaplay.
|**`options`**|object|Options used to describe table
|**`components`**|object|Custom components used to render the table

Expand Down Expand Up @@ -259,7 +259,8 @@ const columns = [
#### Column Options:
|Name|Type|Default|Description
|:--:|:-----|:--|:-----|
|**`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`|` React Component` [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/component/index.js)
|**`customBodyRender`**|function||Function that returns a string or React component. Used to display data within all table cells of a given column. The value returned from this function will be used for filtering in the filter dialog. If this isn't need, you may want to consider customBodyRenderLite instead. `function(value, tableMeta, updateValue) => string`|` React Component` [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/component/index.js)
|**`customBodyRenderLite`**|function||Function that returns a string or React component. Used to display data within all table cells of a given column. This method performs better than customBodyRender but has the following caveats: <p><ul><li>The value returned from this function is **not** used for filtering, so the filter dialog will use the raw data from the data array.</li><li>This method only gives you the dataIndex and rowIndex, leaving you to lookup the column value.</li></ul></p>`function(dataIndex, rowIndex) => string`&#124;` React Component` [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/large-data-set/index.js)
|**`customFilterListOptions`**|{render: function, update: function}|| (These options only affect the filter chips that display after filters are selected. To modify the filters themselves, see `filterOptions`) `render` returns a string or array of strings used as the chip label(s). `function(value) => string OR arrayOfStrings`, `update` returns a `filterList (see above)` allowing for custom filter updates when removing the filter chip [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/column-filters/index.js)
|**`customHeadRender`**|function||Function that returns a string or React component. Used as display for column header. `function(columnMeta, handleToggleColumn, sortOrder) => string`&#124;` React Component`
|**`display`**|string|'true'|Display column in table. `enum('true', 'false', 'excluded')`
Expand Down
7 changes: 4 additions & 3 deletions examples/array-value-columns/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,14 @@ class Example extends React.Component {
options: {
filter: true,
filterType: 'multiselect',
customBodyRender: (value) => {
customBodyRenderLite: (dataIndex) => {
let value = data[dataIndex][5];
return value.map( (val, key) => {
return <Chip label={val} key={key} />;
});
}
},
}
}
},
];


Expand Down
5 changes: 2 additions & 3 deletions examples/column-filters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ class Example extends React.Component {
{
name: "Age",
options: {
customBodyRender: (val, tableMeta) => {
console.log(val);
console.dir(tableMeta);
customBodyRenderLite: (dataIndex) => {
let val = data[dataIndex][3];
return val;
},
filter: true,
Expand Down
5 changes: 4 additions & 1 deletion examples/csv-export/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ class Example extends React.Component {
{
name: 'Age',
options: {
customBodyRender: value => <div><span>{value}</span></div>
customBodyRenderLite: (dataIndex) => {
let value = data[dataIndex][3];
return <div><span>{value}</span></div>;
}
}
},
{
Expand Down
9 changes: 4 additions & 5 deletions examples/custom-action-columns/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Example extends React.Component {
filter: false,
sort: false,
empty: true,
customBodyRender: (value, tableMeta, updateValue) => {
customBodyRenderLite: (dataIndex) => {
return (
<button onClick={() => {
const { data } = this.state;
Expand All @@ -69,9 +69,9 @@ class Example extends React.Component {
filter: false,
sort: false,
empty: true,
customBodyRender: (value, tableMeta, updateValue) => {
customBodyRenderLite: (dataIndex, rowIndex) => {
return (
<button onClick={() => window.alert(`Clicked "Edit" for row ${tableMeta.rowIndex}`)}>
<button onClick={() => window.alert(`Clicked "Edit" for row ${rowIndex} with dataIndex of ${dataIndex}`)}>
Edit
</button>
);
Expand Down Expand Up @@ -116,7 +116,7 @@ class Example extends React.Component {
filter: false,
sort: false,
empty: true,
customBodyRender: (value, tableMeta, updateValue) => {
customBodyRenderLite: (dataIndex) => {
return (
<button onClick={() => {
const { data } = this.state;
Expand Down Expand Up @@ -168,7 +168,6 @@ class Example extends React.Component {
filter: true,
filterType: 'dropdown',
responsive: 'vertical',
page: 2,
onColumnSortChange: (changedColumn, direction) => console.log('changedColumn: ', changedColumn, 'direction: ', direction),
onChangeRowsPerPage: numberOfRows => console.log('numberOfRows: ', numberOfRows),
onChangePage: currentPage => console.log('currentPage: ', currentPage)
Expand Down
17 changes: 11 additions & 6 deletions examples/large-data-set/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class Example extends React.Component {
label: "Name",
options: {
filter: true,
customBodyRender: (val) => {
customBodyRenderLite: (dataIndex) => {
let val = this.state.data[dataIndex].name;
return val;
}
}
Expand All @@ -61,7 +62,8 @@ class Example extends React.Component {
label: "Modified Title Label",
options: {
filter: true,
customBodyRender: (val, tableMeta) => {
customBodyRenderLite: (dataIndex) => {
let val = this.state.data[dataIndex].title;
return val;
}
}
Expand All @@ -86,7 +88,8 @@ class Example extends React.Component {
options: {
filter: true,
sort: false,
customBodyRender: (val) => {
customBodyRenderLite: (dataIndex) => {
let val = this.state.data[dataIndex].salary;
return val;
}
}
Expand All @@ -97,7 +100,8 @@ class Example extends React.Component {
options: {
filter: true,
sort: false,
customBodyRender: (val) => {
customBodyRenderLite: (dataIndex) => {
let val = this.state.data[dataIndex].phone;
return val;
}
}
Expand All @@ -108,7 +112,8 @@ class Example extends React.Component {
options: {
filter: true,
sort: false,
customBodyRender: (val) => {
customBodyRenderLite: (dataIndex) => {
let val = this.state.data[dataIndex].email;
return val;
}
}
Expand Down Expand Up @@ -144,4 +149,4 @@ class Example extends React.Component {
}
}

export default Example;
export default Example;
70 changes: 40 additions & 30 deletions src/MUIDataTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class MUIDataTable extends React.Component {
filterType: PropTypes.oneOf(['dropdown', 'checkbox', 'multiselect', 'textField', 'custom']),
customHeadRender: PropTypes.func,
customBodyRender: PropTypes.func,
customBodyRenderLite: PropTypes.func,
customFilterListOptions: PropTypes.oneOfType([
PropTypes.shape({
render: PropTypes.func,
Expand Down Expand Up @@ -671,16 +672,6 @@ class MUIDataTable extends React.Component {
})
: data.map(row => columns.map(col => leaf(row, col.name)));

// We need to determine if object data exists in the transformed structure, as this is currently not allowed and will cause errors if not handled by a custom renderer
const hasInvalidData =
transformedData.filter(
data => data.filter(d => typeof d === 'object' && d !== null && !Array.isArray(d)).length > 0,
).length > 0;
if (hasInvalidData)
this.warnDep(
'Passing objects in as data is not supported. Consider using ids in your data and linking it to external objects if you want to access object data from custom render functions.',
);

return transformedData;
};

Expand Down Expand Up @@ -726,26 +717,41 @@ class MUIDataTable extends React.Component {
});
}

if (typeof column.customBodyRender === 'function') {
const rowData = tableData[rowIndex].data;
tableMeta = this.getTableMeta(rowIndex, colIndex, rowData, column, data, this.state);
const funcResult = column.customBodyRender(value, tableMeta);
if (column.filter !== false) {
if (typeof column.customBodyRender === 'function') {
const rowData = tableData[rowIndex].data;
tableMeta = this.getTableMeta(rowIndex, colIndex, rowData, column, data, this.state);
const funcResult = column.customBodyRender(value, tableMeta);

if (React.isValidElement(funcResult) && funcResult.props.value) {
value = funcResult.props.value;
} else if (typeof funcResult === 'string') {
value = funcResult;
if (React.isValidElement(funcResult) && funcResult.props.value) {
value = funcResult.props.value;
} else if (typeof funcResult === 'string') {
value = funcResult;
}
}
}

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 (typeof value === 'object' && !Array.isArray(value) && value !== null) {
// it's extremely rare but possible to create an object without a toString method, ex: var x = Object.create(null);
// so this check has to be made
value = value.toString ? value.toString() : '';
}

if (filterData[colIndex].indexOf(value) < 0 && !Array.isArray(value)) {
filterData[colIndex].push(value);
} else if (Array.isArray(value)) {
value.forEach(element => {
let elmVal;
if ((typeof element === 'object' && element !== null) || typeof element === 'function') {
elmVal = element.toString ? element.toString() : '';
} else {
elmVal = element;
}

if (filterData[colIndex].indexOf(elmVal) < 0) {
filterData[colIndex].push(elmVal);
}
});
}
}
}

Expand Down Expand Up @@ -895,7 +901,9 @@ class MUIDataTable extends React.Component {
let columnValue = row[index];
let column = columns[index];

if (column.customBodyRender) {
if (column.customBodyRenderLite) {
displayRow.push(column.customBodyRenderLite);
} else if (column.customBodyRender) {
const tableMeta = this.getTableMeta(rowIndex, index, row, column, dataForTableMeta, {
...this.state,
filterList: filterList,
Expand All @@ -916,9 +924,11 @@ class MUIDataTable extends React.Component {
: funcResult.props && funcResult.props.value
? funcResult.props.value
: columnValue;
}

displayRow.push(columnDisplay);
displayRow.push(columnDisplay);
} else {
displayRow.push(columnDisplay);
}

const columnVal = columnValue === null || columnValue === undefined ? '' : columnValue.toString();

Expand Down
2 changes: 1 addition & 1 deletion src/components/TableBodyCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function TableBodyCell(props) {
className,
)}
{...otherProps}>
{children}
{typeof children === 'function' ? children(dataIndex, rowIndex) : children}
</div>,
];

Expand Down
77 changes: 77 additions & 0 deletions test/MUIDataTable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1938,4 +1938,81 @@ describe('<MUIDataTable />', function() {
expect(getCollatorComparator()('testStringA', 'testStringB')).to.equal(-1);
});
});

it('should correctly execute customBodyRender methods based on filtering and data', () => {
let filteredData = [];
let customBodyRenderCb = spy();
let customBodyRenderLiteCb = spy();
let customBodyRenderNoFilterCb = spy();
const options = {
rowsPerPage: 5,
rowsPerPageOptions: [5]
};

const data = [
['a','b'],
['a','b'],
['a','b'],
['a','b'],
['a','b'],

['a','b'],
['a','b'],
['a','b'],
['a','b'],
['a','b'],

['a','b'],
['a','b'],
['a','b'],
['a','b'],
['a','b'],
];

const columns = [
{
name: 'firstName',
label: 'First Name',
options: {
customBodyRender: () => {
customBodyRenderCb();
return '';
}
}
},
{
name: 'lastName',
label: 'Last Name',
options: {
customBodyRenderLite: () => {
customBodyRenderLiteCb();
return '';
}
},
},
{
name: 'phone',
label: 'Phone',
options: {
filter: false,
customBodyRender: () => {
customBodyRenderNoFilterCb();
return '';
}
},
}
];

const fullWrapper = mount(<MUIDataTable columns={columns} data={data} options={options} />);
fullWrapper.unmount();

// lite only gets executed for the 5 entries shown
assert.strictEqual(customBodyRenderLiteCb.callCount, 5);

// regular gets executed 15 times for filtering, and 15 more times for display
assert.strictEqual(customBodyRenderCb.callCount, 30);

// regular with no filtering gets executed 15 times for display
assert.strictEqual(customBodyRenderNoFilterCb.callCount, 15);
});
});

0 comments on commit 77170f7

Please sign in to comment.