Is your feature request related to a problem? Please describe.
We are using material-table to export csv files. But when any column or column's title has a custom render that returns a React.Node (ex i18n Translate component) it get exported as [object Object].
Describe the solution you'd like
It would be usefull to have the option to provide a custom render function (row: RowData): string and (title: ReactNode): string for each column and column's title. And those functions used when provided to render csv data.
Describe alternatives you've considered
Additional context
How are you setting the title? Are you using the render attribute on the columns prop, like here?
Hi there I'm setting columns like this
import MaterialTable, { MTableToolbar } from 'material-table';
<MaterialTable
// other props
columns={[
{
field: 'someField',
title: <Translate value="translationkey" />,
render: rowData => <Translate value={rowData.translationKey} />
}
]}
/>
Translate component from react-i18n. So my columns are all text based but when exporting csv I have problems with columns titles and translated columns.
@thefat32 it looks like you're going to have to add your own export logic to resolve this..
In the example below, the component I am using as the title contains a prop called text, which stores the text based representation of what is rendered on screen. I am not familiar with the Translate component you are using, so I am not sure of the props it contains, but I assume you can drill into the object and grab the text based representation.
A live demo of the code below can be found here
// MyComponent.js
import React from "react";
const MyComponent = ({ text }) => <h1>{text}</h1>
export default MyComponent
// App.js
import React, { useRef } from 'react';
import { render } from 'react-dom';
import MaterialTable from "material-table";
import tableIcons from "./TableIcons.js";
import MyComponent from "./MyComponent.js";
const rando = max => Math.floor(Math.random() * max);
const data = ["Rock", "Paper", "Scissors"].map(e => ({ id: rando(300 + e.length), name: e }));
const downloadCsv = (data, fileName) => {
const finalFileName = fileName.endsWith(".csv") ? fileName : `${fileName}.csv`;
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([data], { type: "text/csv" }));
a.setAttribute("download", finalFileName);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
const App = () => {
const tableRef = useRef();
return (
<div>
<MaterialTable
tableRef={tableRef}
data={data}
icons={tableIcons}
columns={[
{
field: "id",
title: <MyComponent text="Id" />,
render: rowData => <MyComponent text={rowData.id} />
},
{
field: "name",
title: <MyComponent text="Name" />,
render: rowData => <MyComponent text={rowData.name} />
}
]}
options={{
exportButton: true,
exportCsv: (columns, data) => {
// Turn headers into array of strings
const headerRow = columns.map(col => {
if (typeof col.title === 'object') {
// I am not sure what props the Translate component exposes
// but you would need to change `text` in `col.title.props.text`
// to whatever prop you need.
return col.title.props.text;
}
return col.title;
});
// Turn data into an array of string arrays, without the `tableData` prop
const dataRows = data.map(({ tableData, ...row }) => Object.values(row));
// Aggregate header data and 'body' data
// Mirror default export behavior by joining data via
// the delimiter specified within material table (by default comma delimited)
const { exportDelimiter } = tableRef.current.props.options;
const delimiter = exportDelimiter ? exportDelimiter : ",";
const csvContent = [headerRow, ...dataRows].map(e => e.join(delimiter)).join("\n");
// This mirrors the default export behavior where the
// exported file name is the table title.
const csvFileName = tableRef.current.props.title;
// Allow user to download file as .csv
downloadCsv(csvContent, csvFileName);
}
}}
/>
</div>
);
}
render(<App />, document.getElementById('root'));
Great example! Thanks!
@thefat32 no problem, glad to help! If this resolved the problem you were having, would you mind closing this issue out?
I still think it would be nice to have a pair of functions in the column definition, so they can be used in the default exportCsv funtion. This way there is no need for the user to understand the csv generation process. My proposal is something like this:
interface Column<RowData extends object> {
...otherProps
titleAsText?: () => string;
getValueAsText?: (data: RowData) => string;
}
And in default exportCsv function
defaultExportCsv = () => {
const columns = this.props.columns
.filter(columnDef => {
return !columnDef.hidden && columnDef.field && columnDef.export !== false;
})
.sort((a, b) => (a.tableData.columnOrder > b.tableData.columnOrder) ? 1 : -1);
const dataToExport = this.props.exportAllData ? this.props.data : this.props.renderData;
const data = dataToExport.map(rowData =>
columns.map(columnDef => {
//return this.props.getFieldValue(rowData, columnDef);
return columnDef.getValueAsText ? columnDef.getValueAsText(rowData) : this.props.getFieldValue(rowData, columnDef);
})
);
const builder = new CsvBuilder((this.props.exportFileName || this.props.title || 'data') + '.csv');
builder
.setDelimeter(this.props.exportDelimiter)
//.setColumns(columns.map(columnDef => columnDef.title))
.setColumns(columns.map(columnDef => columnDef.titleAsText ? columnDef.titleAsText() : columnDef.title))
.addRows(data)
.exportFile();
}
What do you think about it?
If you think this approach could make it through I will make a PR
@thefat32 unfortunately, I do not have access to approve/merge PRs, and those that do have those permissions are not exactly responsive.
I went ahead and created a fork of this repo that will be maintained. It will always be 100% compatible with material-table and I do not plan on making dramatic changes to the code base. Ultimately, my goal is to knock out issues and PRs - if material table ever wants me to send them a PR with anything I update I'd be happy to do so. You can read more here..
My point is if you wanted to fork that repo and send a PR to it, I would gladly accept it.
Well I'd gladly present the PR in the two repos when I got the time. I asume the goal of your project is to extend functionallity and fix bugs while mantaining backwards compatibility with official material-table package. Am I right? I wasn't aware of the lack of responsiveness from material-table team. Your approach seems a good a idea to me, so material-table develoment won't stall and official package could be upgraded from your repo if they decided to. Ultimately people could rely in your package meanwhile.
No worries.. what I'm getting at is, your PR won't get merged here for like 6 months. They're not very responsive.
@thefat32 I'd like to propose to use getValueAsText also in search context. What do you think?
We've move our dependency to oze4 repo, will create the issue there and post the link here aftwerwards so we can continue the discussion there
We move here https://github.com/oze4/material-table-core/issues/2
/mtc::resolved
This has been resolved in @material-table/core - You can use the titleAsText: () => "Your Text Title" column property for this. Check out a code example here, as well as a live demo here
See PR for more info
@mbrn Do you think we can bring https://github.com/oze4/material-table-core/pull/3 to material-table?
Most helpful comment
We've move our dependency to oze4 repo, will create the issue there and post the link here aftwerwards so we can continue the discussion there