gregnb / mui-datatables

Datatables for React using Material-UI
MIT License
2.7k stars 930 forks source link

Invoke "download to csv" function when clicking on a custom icon in customToolbarSelect #1260

Open Lotti opened 4 years ago

Lotti commented 4 years ago

I'm building a data table with your widget, and I'd like to make the user download to csv only the currently selected records, without have to replicate all the code needed to transform data in to CSV and implementing the "onDownload" callback, filtering out not selected rows.

Expected Behavior

I was expecting some sort of hook or function to invoke, within CustomToolbarSelect callback

Current Behavior

It seems impossible (by looking at documentation and sourcecode) to invoke the "download to csv" function (handleCSVDownload) when clicking over a custom icon/button put inside the CustomToolbarSelect

Your Environment

Tech Version
Material-UI 4.9.10
MUI-datatables 2.14.0
React 16.13.1
browser Firefox
etc
hasanbisha commented 4 years ago

I've struggled with this problem too, found a solution but it's not the best. the idea create the csv string yourself from the selected rows, transform the string into a blob and then download it. im giving some code so you can start somewhere (also, if you find a better solution, please share). i hope it helps. if you dont understand something, feel free to ask

import React from 'react';
import DataTable from 'mui-datatables';
import { IconButton } from '@material-ui/core';
import { CloudDownload } from '@material-ui/icons';

const data = [
  ['Gabby George', 'Business Analyst', 'Minneapolis', 30, 100000],
  ['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],
  ['Blake Duncan', 'Business Management Analyst', 'San Diego', 65, 94000],
  ['Frankie Parry', 'Agency Legal Counsel', 'Jacksonville', 71, 210000],
  ['Lane Wilson', 'Commercial Specialist', 'Omaha', 19, 65000],
  ['Robin Duncan', 'Business Analyst', 'Los Angeles', 20, 77000],
  ['Mel Brooks', 'Business Consultant', 'Oklahoma City', 37, 135000],
  ['Harper White', 'Attorney', 'Pittsburgh', 52, 420000],
  ['Kris Humphrey', 'Agency Legal Counsel', 'Laredo', 30, 150000],
  ['Frankie Long', 'Industrial Analyst', 'Austin', 31, 170000],
  ['Brynn Robbins', 'Business Analyst', 'Norfolk', 22, 90000],
  ['Justice Mann', 'Business Consultant', 'Chicago', 24, 133000],
  ['Addison Navarro', 'Business Management Analyst', 'New York', 50, 295000],
  ['Jesse Welch', 'Agency Legal Counsel', 'Seattle', 28, 200000],
  ['Eli Mejia', 'Commercial Specialist', 'Long Beach', 65, 400000],
  ['Gene Leblanc', 'Industrial Analyst', 'Hartford', 34, 110000],
  ['Danny Leon', 'Computer Scientist', 'Newark', 60, 220000],
  ['Lane Lee', 'Corporate Counselor', 'Cincinnati', 52, 180000],
  ['Jesse Hall', 'Business Analyst', 'Baltimore', 44, 99000],
  ['Danni Hudson', 'Agency Legal Counsel', 'Tampa', 37, 90000],
  ['Terry Macdonald', 'Commercial Specialist', 'Miami', 39, 140000],
  ['Justice Mccarthy', 'Attorney', 'Tucson', 26, 330000],
  ['Silver Carey', 'Computer Scientist', 'Memphis', 47, 250000],
  ['Franky Miles', 'Industrial Analyst', 'Buffalo', 49, 190000],
  ['Glen Nixon', 'Corporate Counselor', 'Arlington', 44, 80000],
  ['Gabby Strickland', 'Business Process Consultant', 'Scottsdale', 26, 45000],
  ['Mason Ray', 'Computer Scientist', 'San Francisco', 39, 142000],
];

const columns = [
  { label: 'Name', name: 'name' },
  { label: 'Title', name: 'title' },
  { label: 'Location', name: 'Location' },
  { label: 'Age', name: 'age' },
  { label: 'Salary', name: 'salary' },
];

// gets from all the data the selected rows
const getRowsToBeDownloaded = (selectedRows, data) => {
  return data.filter(row => selectedRows.data.some(selected => selected.dataIndex === row.dataIndex)).map(row => row.data);
}

// this function return the csv string
const getCsvStringFromArrayOfStrings = (columns, data) => {
  // maps through the columns array and generates the csv header
  const csvHeader = columns.map(column => `"${column.label}"`).join();
  // maps through each row and adds a new line so the next row can be generated
  const csvBody = data.map(row => row.map(cell => cell !== null ? `"${cell}"` : '""').join()).join('\n');
  // joins together the csv header and body and puts a new line in between of them
  return `${csvHeader}\n${csvBody}`;
};

// i know it is disgusting but dont judge me
// creates an artificial link, clicks it automatically so it downloads immediately and it removes it 
const saveCsvStringAsFile = (csvString, fileName) => {
  const url = window.URL.createObjectURL(new Blob([csvString]));
  const link = document.createElement('a');
  link.href = url;
  link.setAttribute('download', `${fileName.replace(' ', '')}.csv`);
  document.body.appendChild(link);
  link.click();
  link.parentNode.removeChild(link);
};

const downloadRowsAsCSV = (rows, columns, fileName) => {
  // generate the csv string
  const csvString = getCsvStringFromArrayOfStrings(columns, rows);

  // some disgusting way of downloading the csv but it works i guess
  saveCsvStringAsFile(csvString, fileName);
}

const SelectedRowsToolbar = ({ selectedRows, data, columns, datatableTitle }) => {
  console.log(selectedRows, data, columns, datatableTitle);
  return (
    <div>
      <IconButton onClick={() => downloadRowsAsCSV(getRowsToBeDownloaded(selectedRows, data), columns, datatableTitle)}>
        <CloudDownload />
      </IconButton>
    </div>
  );
}

export default () => {
  return (
    <DataTable 
      title="test"
      data={data}
      columns={columns}
      options={{
        customToolbarSelect: (selectedRows, data) =>
          <SelectedRowsToolbar
            selectedRows={selectedRows}
            data={data}
            columns={columns}
            datatableTitle="test"
          />,
      }}
    />
  );
};
Lotti commented 4 years ago

thank you for your reply and code, very kind.

I had to do about the same work you did by figuring out how the original functionality worked and copy/pasted all the original code in my app :/

Anyway, thank you again, have a good week.