Downloading doesn't work without callbacks
Title Downloading doesn't work out of the box.
Short Description: You have to add dummy downloadDataFormatter and downloadReady callbacks to the options to make it work. I don't know if this is by design or not. If you do not add these callbacks then when tabulator-tables calls these functions react-tabulator calls a NOOPS dummy function that does nothing and therefore causes errors.
Environment Details
- OS: Linux Debian 8
- Node.js version: 9.11.2
- npm version: 5.6.0
- react version: 16.5.2
- tabulator-tables version: 4.2.3 (included with react-tabulator)
- react-tabulator version: 0.9.1
Long Description I'm trying to download a csv of the table data which uses grouped columns. My first attempt was to simply create a reference to the ReactTabulator component and call the following function as per tabulator's instructions:
this.tableRef.current.table.download("csv", "data.csv")
This causes the following error:
TypeError: Cannot read property 'forEach' of undefined parseRows
node_modules/react-tabulator/node_modules/tabulator-tables/dist/js/tabulator.js:9608 9605 | 9606 | function parseRows(data) { 9607 | //generate each row of the table 9608 | data.forEach(function (row) { | ^ 9609 | var rowData = []; 9610 | fields.forEach(function (field) { 9611 | var value = self.getFieldValue(field, row);
What is happening is that the data is undefined as it is being overwritten by the following code:
Download.download node_modules/react-tabulator/node_modules/tabulator-tables/dist/js/tabulator.js:9336 9333 | this.processColumns(); 9334 | 9335 | if (downloadFunc) { 9336 | downloadFunc.call(this, self.processDefinitions(), self.processData(), options || {}, buildLink, this.config); | ^ 9337 | } 9338 | };
As we have not set a downloadDataFormatter option which would be stored in downloadFunc, react-tabulator provides a NOOPS function that returns undefined.
var NOOPS = function NOOPS() {}; // get callbacks from props (default: NOOP) to set them to Tabulator options later.
Ok so we can fix this by implementing a downloadDataFormatter as shown in the workaround and then this error goes away. However we then have a new issues and the download fails silently. This is now due to tabulator-tables calling a downloadReady callback which also does not exist and therefore calls the NOOPs dummy function that returns undefined. The downloadReady expects to either return a blob which then triggers the actual download or if undefined then stops further processing. see docs
9514 | Download.prototype.triggerDownload = function (data, mime, type, filename, newTab) { 9515 | var element = document.createElement('a'), 9516 | blob = new Blob([data], { 9517 | type: mime 9518 | }), 9519 | filename = filename || "Tabulator." + (typeof type === "function" ? "txt" : type); 9520 | blob = this.table.options.downloadReady.call(this.table, data, blob); 9521 | ^ fails here
Workaround To make the download work correctly you simply need to provide the two missing callbacks in the tabulator options:
<ReactTabulator
ref={this.tableRef}
columns={columns}
data={items}
options={options}
/>
...
options = {
downloadDataFormatter: (data) => data,
downloadReady: (fileContents, blob) => blob,
}
Possible solutions would be for react-tabulator to provide these required options instead of calling a NOOPS function which would therefore work the same was as the tabulator docs or provide information in the react-tabulator docs to state that these callbacks are necessary.
I hope this is useful to someone :)
Thank you so much @FabriceReynolds this was making me crazy!
I am also having troubles downloading table data to a file.
Below is my .js file. I've added the dummy downloadDataFormatter and downloadReady callbacks to the options. I have an onClick to call a downloadData function but I receive:
Uncaught TypeError: _this.download is not a function
As per the Tabulator documentation I added the following to my index.html but did not help:
<script type="text/javascript" src="https://oss.sheetjs.com/sheetjs/xlsx.full.min.js"></script>
I also tried installing:
$ npm install xlsx
and added to my .js:
import * as XLSX from 'xlsx';
with no luck either. Honestly I'm not even sure this is the correct node module I would need. I just took a stab at it based on https://oss.sheetjs.com/sheetjs/xlsx.full.min.js
Any help is appreciated. Thank you.
import React from 'react';
import {Row, Col, Card} from 'react-bootstrap';
import Aux from "../../hoc/_Aux";
import 'react-tabulator/lib/styles.css';
import { ReactTabulator } from 'react-tabulator';
import 'react-tabulator/lib/css/tabulator.min.css'; // theme
const columns = [
{ title: "Area", field: "Area", align: "center" },
{ title: "Cash Flow (Mo)", field: "Cash Flow (Mo)", align: "right" },
{ title: "ROI", field: "ROI", align: "right" },
{ title: "Total Rent", field: "Total Rent", align: "right" },
{ title: "Downpayment", field: "Downpayment", align: "right" },
{ title: "Loan Balance", field: "Loan Balance", align: "right" },
{ title: "P&I", field: "P&I", align: "right" },
{ title: "PITI", field: "PITI", align: "right" },
{ title: "Taxes (Mo)", field: "Taxes (Mo)", align: "right" },
{ title: "Ins (Mo)", field: "Ins (Mo)", align: "right" }
];
class TabulatorTable extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [],
errorMsg: ""
};
}
componentDidMount() {
this.fetchData();
}
fetchData = () => {
fetch("http://127.0.0.1:5000/properties?apikey=abc123xyz&rate=" + document.getElementById('rate').value + "&downpayment=" + document.getElementById('downpayment').value + "&insurance=" + document.getElementById('insurance').value + "&closingcosts=" + document.getElementById('closing-costs').value + "&managementfee=" + document.getElementById('management-fee').value )
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.properties
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
downloadData = () => {
this.download("csv", "data.csv");
};
render() {
const { items, error } = this.state;
const options = {
pagination: 'local',
paginationSize:20,
paginationSizeSelector:[20, 50, 100],
layout:'fitDataFill',
movableColumns: true,
selectable:true,
downloadDataFormatter: (data) => data,
downloadReady: (fileContents, blob) => blob
};
return (
<Aux>
<Row>
<Col>
<Card>
<Card.Header>
<Card.Title as="h5">Available Properties</Card.Title>
</Card.Header>
<Card.Body>
<button onClick={this.downloadData}>Download</button>
<ReactTabulator
data={items}
columns={columns}
tooltips={true}
layout={"fitData"}
options={options}
/>
</Card.Body>
</Card>
</Col>
</Row>
</Aux>
);
}
}
export default TabulatorTable;
Thanks a lot @FabriceReynolds for the workaround ! Saved my day !
@psaban20 I just managed to get this to work with the button you've got by adding a reference to the table, the reason you're getting this.download doesn't exist is that "this" was referring to the instance of the button in the jsx..
Add the ref below along with the external js file and you should be good to go. p.s. I only figured it out from using you code, which helped me get unstuck, so thank you !.
class TabulatorTable extends React.Component {
ref = null;
....
downloadData = () => {
this.ref.table.download("xlsx", "data.xlsx");
};
.....
return (
<div>
<button type="button" onClick={this.downloadData} className="btn btn-success">Download XLSX</button>
<ReactTabulator
ref={ref => (this.ref = ref)}
data={data}
columns={columns}
........
/>
</div>
);
WHAT A LIFE SAVIOR. THANK YOU! @FabriceReynolds ❤
Thanks @FabriceReynolds I added your workaround here: https://github.com/ngduc/react-tabulator/blob/master/docs/examples.md I'll investigate and find a proper solution.