Skip to content

Commit ea16848

Browse files
refactor (lib)
1 parent 48fb86b commit ea16848

File tree

5 files changed

+200
-0
lines changed

5 files changed

+200
-0
lines changed

lib/components/ExcelFile.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import {saveAs} from "file-saver";
4+
import XLSX from "xlsx";
5+
6+
import ExcelSheet from "../elements/ExcelSheet";
7+
import {strToArrBuffer, excelSheetFromAoA} from "../utils/DataUtil";
8+
9+
class ExcelFile extends React.Component {
10+
static props = {
11+
filename: PropTypes.string,
12+
element: PropTypes.any,
13+
children: function (props, propName, componentName) {
14+
React.Children.forEach(props[propName], child => {
15+
if (child.type !== ExcelSheet) {
16+
throw new Error('<ExcelFile> can only have <ExcelSheet> as children. ');
17+
}
18+
});
19+
}
20+
};
21+
22+
static defaultProps = {
23+
filename: "Download.xlsx",
24+
element: <button>Download</button>
25+
};
26+
27+
constructor(props) {
28+
super(props);
29+
30+
this.handleDownload = this.download.bind(this);
31+
this.createSheetData = this.createSheetData.bind(this);
32+
}
33+
34+
createSheetData(sheet) {
35+
const columns = sheet.props.children;
36+
const sheetData = [React.Children.map(columns, column => column.props.label)];
37+
const data = typeof (sheet.props.data) === 'function' ? sheet.props.data() : sheet.props.data;
38+
39+
data.forEach(row => {
40+
const sheetRow = [];
41+
42+
React.Children.forEach(columns, column => {
43+
const getValue = typeof (column.props.value) === 'function' ? column.props.value : row => row[column.props.value];
44+
sheetRow.push(getValue(row) || '');
45+
});
46+
47+
sheetData.push(sheetRow);
48+
});
49+
50+
return sheetData;
51+
}
52+
53+
download() {
54+
const wb = {
55+
SheetNames: React.Children.map(this.props.children, sheet => sheet.props.name),
56+
Sheets: {}
57+
};
58+
59+
React.Children.forEach(this.props.children, sheet => {
60+
wb.Sheets[sheet.props.name] = excelSheetFromAoA(this.createSheetData(sheet));
61+
});
62+
63+
const wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: true, type: 'binary'});
64+
saveAs(new Blob([strToArrBuffer(wbout)], {type: "application/octet-stream"}), this.props.filename);
65+
}
66+
67+
render() {
68+
return (<span onClick={this.handleDownload}>{this.props.element}</span>);
69+
}
70+
}
71+
72+
export default ExcelFile;

lib/elements/ExcelColumn.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
4+
5+
export default class ExcelColumn extends React.Component {
6+
static propsTypes = {
7+
label: PropTypes.string.isRequired,
8+
value: PropTypes.oneOfType([
9+
PropTypes.number,
10+
PropTypes.bool,
11+
PropTypes.string,
12+
PropTypes.func
13+
]).isRequired
14+
};
15+
16+
render() {
17+
return null;
18+
}
19+
}

lib/elements/ExcelSheet.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import ExcelColumn from "./ExcelColumn";
4+
5+
6+
export default class ExcelSheet extends React.Component {
7+
static propsTypes = {
8+
name: PropTypes.string.isRequired,
9+
value: PropTypes.oneOfType([
10+
PropTypes.array,
11+
PropTypes.func
12+
]).isRequired,
13+
children: PropTypes.arrayOf((propValue, key) => {
14+
const type = propValue[key];
15+
16+
if (type !== ExcelColumn) {
17+
throw new Error("<ExcelSheet> can only have <ExcelColumn> as children");
18+
}
19+
}).isRequired
20+
};
21+
22+
render() {
23+
return null;
24+
}
25+
}

lib/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import ExcelFile from "./components/ExcelFile";
2+
import ExcelSheet from "./elements/ExcelSheet";
3+
import ExcelColumn from "./elements/ExcelColumn";
4+
5+
module.exports = {
6+
ExcelFile,
7+
ExcelSheet,
8+
ExcelColumn
9+
};

lib/utils/DataUtil.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import XLSX from "xlsx";
2+
3+
const strToArrBuffer = (s) => {
4+
var buf = new ArrayBuffer(s.length);
5+
var view = new Uint8Array(buf);
6+
7+
for (var i = 0; i != s.length; ++i) {
8+
view[i] = s.charCodeAt(i) & 0xFF;
9+
}
10+
11+
return buf;
12+
};
13+
14+
const dateToNumber = (v, date1904) => {
15+
if (date1904) {
16+
v += 1462;
17+
}
18+
19+
var epoch = Date.parse(v);
20+
21+
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
22+
};
23+
24+
const excelSheetFromAoA = (data) => {
25+
var ws = {};
26+
var range = {s: {c: 10000000, r: 10000000}, e: {c: 0, r: 0}};
27+
28+
for (var R = 0; R != data.length; ++R) {
29+
for (var C = 0; C != data[R].length; ++C) {
30+
if (range.s.r > R) {
31+
range.s.r = R;
32+
}
33+
34+
if (range.s.c > C) {
35+
range.s.c = C;
36+
}
37+
38+
if (range.e.r < R) {
39+
range.e.r = R;
40+
}
41+
42+
if (range.e.c < C) {
43+
range.e.c = C;
44+
}
45+
46+
var cell = {v: data[R][C]};
47+
if (cell.v === null) {
48+
continue;
49+
}
50+
51+
var cellRef = XLSX.utils.encode_cell({c: C, r: R});
52+
if (typeof cell.v === 'number') {
53+
cell.t = 'n';
54+
} else if (typeof cell.v === 'boolean') {
55+
cell.t = 'b';
56+
} else if (cell.v instanceof Date) {
57+
cell.t = 'n';
58+
cell.z = XLSX.SSF._table[14];
59+
cell.v = dateToNumber(cell.v);
60+
} else {
61+
cell.t = 's';
62+
}
63+
64+
ws[cellRef] = cell;
65+
}
66+
}
67+
68+
if (range.s.c < 10000000) {
69+
ws['!ref'] = XLSX.utils.encode_range(range);
70+
}
71+
72+
return ws;
73+
};
74+
75+
export {strToArrBuffer, dateToNumber, excelSheetFromAoA};

0 commit comments

Comments
 (0)