├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── example
├── index.html
└── index.jsx
├── index.js
├── package.json
├── src
└── ReactHTMLTableToExcel.jsx
└── test
└── index.test.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [ "es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "plugins": [
5 | "react",
6 | "jsx-a11y",
7 | "import"
8 | ],
9 | "rules": {
10 | "react/jsx-space-before-closing": "off",
11 | "linebreak-style": ["error", "unix"]
12 | }
13 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # Visual studio code
61 | .vscode/
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 | script: npm run client:test
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 zsusac
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ReactHTMLTableToExcel
2 | Provides a client side generation of Excel (.xls) file from HTML table element.
3 |
4 | [](https://travis-ci.org/zsusac/ReactHTMLTableToExcel)
5 |
6 | [](https://npmjs.org/package/react-html-table-to-excel)
7 |
8 | ___
9 | No additional dependencies
10 | ___
11 |
12 | ## Installation
13 |
14 | ```
15 | npm install --save react-html-table-to-excel
16 | ```
17 |
18 | ## Features
19 |
20 | * Download HTML table as Excel file in .xls format
21 | * No server side code
22 | * Set desired .xls filename and sheet
23 | * Set desired class name and id for styling
24 | * Supported IE 11
25 |
26 | ## Options
27 |
28 | A list of available properties can be found below. These must be passed to the containing `ReactHTMLTableToExcel` component.
29 |
30 | Property | Type | Description
31 | ----- | ----- | -----
32 | **table** | *string* | ID attribute of HTML table element.
33 | **filename** | *string* | Name of Excel file.
34 | **sheet** | *string* | Name of Excel sheet.
35 | **id** | *string* | ID attribute of button element.
36 | **className** | *string* | Class attribute of button element.
37 | **buttonText** | *string* | Button text.
38 |
39 |
40 | ## Example
41 |
42 | ```javascript
43 | import React, {Component} from 'react';
44 | import ReactHTMLTableToExcel from 'react-html-table-to-excel';
45 |
46 | class Test extends Component {
47 |
48 | constructor(props) {
49 | super(props);
50 | }
51 |
52 | render() {
53 |
54 | return (
55 |
56 |
63 |
64 |
65 | Firstname |
66 | Lastname |
67 | Age |
68 |
69 |
70 | Jill |
71 | Smith |
72 | 50 |
73 |
74 |
75 | Eve |
76 | Jackson |
77 | 94 |
78 |
79 |
80 |
81 |
82 | );
83 | }
84 | }
85 |
86 | export default Test
87 | ```
88 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ReactHTMLTableToExcel Example
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/index.jsx:
--------------------------------------------------------------------------------
1 | /* global document */
2 | import React, { Component } from 'react';
3 | import ReactDOM from 'react-dom';
4 | import ReactHTMLTableToExcel from 'react-html-table-to-excel';
5 |
6 | class Example extends Component {
7 |
8 | render() {
9 | return (
10 |
11 |
19 |
20 |
21 | Firstname |
22 | Lastname |
23 | Age |
24 |
25 |
26 | Jill |
27 | Smith |
28 | 50 |
29 |
30 |
31 | Eve |
32 | Jackson |
33 | 94 |
34 |
35 |
36 |
37 | );
38 | }
39 | }
40 |
41 | ReactDOM.render(, document.getElementById('example'));
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8 |
9 | var _react = require('react');
10 |
11 | var _react2 = _interopRequireDefault(_react);
12 |
13 | var _propTypes = require('prop-types');
14 |
15 | var _propTypes2 = _interopRequireDefault(_propTypes);
16 |
17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
18 |
19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
20 |
21 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
22 |
23 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* global window, document, Blob */
24 |
25 |
26 | var propTypes = {
27 | table: _propTypes2.default.string.isRequired,
28 | filename: _propTypes2.default.string.isRequired,
29 | sheet: _propTypes2.default.string.isRequired,
30 | id: _propTypes2.default.string,
31 | className: _propTypes2.default.string,
32 | buttonText: _propTypes2.default.string
33 | };
34 |
35 | var defaultProps = {
36 | id: 'button-download-as-xls',
37 | className: 'button-download',
38 | buttonText: 'Download'
39 | };
40 |
41 | var ReactHTMLTableToExcel = function (_Component) {
42 | _inherits(ReactHTMLTableToExcel, _Component);
43 |
44 | function ReactHTMLTableToExcel(props) {
45 | _classCallCheck(this, ReactHTMLTableToExcel);
46 |
47 | var _this = _possibleConstructorReturn(this, (ReactHTMLTableToExcel.__proto__ || Object.getPrototypeOf(ReactHTMLTableToExcel)).call(this, props));
48 |
49 | _this.handleDownload = _this.handleDownload.bind(_this);
50 | return _this;
51 | }
52 |
53 | _createClass(ReactHTMLTableToExcel, [{
54 | key: 'handleDownload',
55 | value: function handleDownload() {
56 | if (!document) {
57 | if (process.env.NODE_ENV !== 'production') {
58 | console.error('Failed to access document object');
59 | }
60 |
61 | return null;
62 | }
63 |
64 | if (document.getElementById(this.props.table).nodeType !== 1 || document.getElementById(this.props.table).nodeName !== 'TABLE') {
65 | if (process.env.NODE_ENV !== 'production') {
66 | console.error('Provided table property is not html table element');
67 | }
68 |
69 | return null;
70 | }
71 |
72 | var table = document.getElementById(this.props.table).outerHTML;
73 | var sheet = String(this.props.sheet);
74 | var filename = String(this.props.filename) + '.xls';
75 |
76 | var uri = 'data:application/vnd.ms-excel;base64,';
77 | var template = '{table}';
78 |
79 | var context = {
80 | worksheet: sheet || 'Worksheet',
81 | table: table
82 | };
83 |
84 | // If IE11
85 | if (window.navigator.msSaveOrOpenBlob) {
86 | var fileData = ['' + ('') + table + ''];
87 | var blobObject = new Blob(fileData);
88 | document.getElementById('react-html-table-to-excel').click()(function () {
89 | window.navigator.msSaveOrOpenBlob(blobObject, filename);
90 | });
91 |
92 | return true;
93 | }
94 |
95 | var element = window.document.createElement('a');
96 | element.href = uri + ReactHTMLTableToExcel.base64(ReactHTMLTableToExcel.format(template, context));
97 | element.download = filename;
98 | document.body.appendChild(element);
99 | element.click();
100 | document.body.removeChild(element);
101 |
102 | return true;
103 | }
104 | }, {
105 | key: 'render',
106 | value: function render() {
107 | return _react2.default.createElement(
108 | 'button',
109 | {
110 | id: this.props.id,
111 | className: this.props.className,
112 | type: 'button',
113 | onClick: this.handleDownload
114 | },
115 | this.props.buttonText
116 | );
117 | }
118 | }], [{
119 | key: 'base64',
120 | value: function base64(s) {
121 | return window.btoa(unescape(encodeURIComponent(s)));
122 | }
123 | }, {
124 | key: 'format',
125 | value: function format(s, c) {
126 | return s.replace(/{(\w+)}/g, function (m, p) {
127 | return c[p];
128 | });
129 | }
130 | }]);
131 |
132 | return ReactHTMLTableToExcel;
133 | }(_react.Component);
134 |
135 | ReactHTMLTableToExcel.propTypes = propTypes;
136 | ReactHTMLTableToExcel.defaultProps = defaultProps;
137 |
138 | exports.default = ReactHTMLTableToExcel;
139 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-html-table-to-excel",
3 | "version": "2.0.0",
4 | "description": "Small react component for converting and downloading HTML table to Excel file",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "./node_modules/.bin/babel ./src/ReactHTMLTableToExcel.jsx -o index.js",
8 | "prepublish": "npm run build",
9 | "client:test": "NODE_ENV=test jest",
10 | "client:test:watch": "NODE_ENV=test jest --watch"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/zsusac/ReactHTMLTableToExcel.git"
15 | },
16 | "keywords": [
17 | "react",
18 | "excel",
19 | "table",
20 | "html",
21 | "xls"
22 | ],
23 | "author": "Zvonimir Susac ",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/zsusac/ReactHTMLTableToExcel/issues"
27 | },
28 | "homepage": "https://github.com/zsusac/ReactHTMLTableToExcel#readme",
29 | "jest": {
30 | "rootDir": "./",
31 | "moduleNameMapper": {
32 | "^.+\\.(css|less)$": "/CSSStub.js"
33 | },
34 | "collectCoverage": true,
35 | "coverageDirectory": "/../../coverage",
36 | "verbose": true,
37 | "coveragePathIgnorePatterns": [
38 | "/../../node_modules/"
39 | ]
40 | },
41 | "peerDependencies": {
42 | "react": "^15.x.x"
43 | },
44 | "devDependencies": {
45 | "babel-cli": "^6.24.1",
46 | "babel-eslint": "^7.2.3",
47 | "babel-preset-es2015": "^6.24.1",
48 | "babel-preset-react": "^6.24.1",
49 | "babel-preset-stage-0": "^6.24.1",
50 | "chai": "^3.5.0",
51 | "chai-enzyme": "^0.6.1",
52 | "enzyme": "^2.8.2",
53 | "eslint": "^3.19.0",
54 | "eslint-config-airbnb": "^14.1.0",
55 | "eslint-plugin-import": "^2.2.0",
56 | "eslint-plugin-jsx-a11y": "^5.0.1",
57 | "eslint-plugin-react": "^7.0.0",
58 | "jest": "^20.0.1",
59 | "react": "^15.5.4",
60 | "react-dom": "^15.5.4",
61 | "react-test-renderer": "^15.5.4",
62 | "sinon": "^2.2.0"
63 | },
64 | "dependencies": {
65 | "prop-types": "^15.5.10"
66 | }
67 | }
--------------------------------------------------------------------------------
/src/ReactHTMLTableToExcel.jsx:
--------------------------------------------------------------------------------
1 | /* global window, document, Blob */
2 | import React, { Component } from 'react';
3 | import PropTypes from 'prop-types';
4 |
5 | const propTypes = {
6 | table: PropTypes.string.isRequired,
7 | filename: PropTypes.string.isRequired,
8 | sheet: PropTypes.string.isRequired,
9 | id: PropTypes.string,
10 | className: PropTypes.string,
11 | buttonText: PropTypes.string,
12 | };
13 |
14 | const defaultProps = {
15 | id: 'button-download-as-xls',
16 | className: 'button-download',
17 | buttonText: 'Download',
18 | };
19 |
20 | class ReactHTMLTableToExcel extends Component {
21 | constructor(props) {
22 | super(props);
23 | this.handleDownload = this.handleDownload.bind(this);
24 | }
25 |
26 | static base64(s) {
27 | return window.btoa(unescape(encodeURIComponent(s)));
28 | }
29 |
30 | static format(s, c) {
31 | return s.replace(/{(\w+)}/g, (m, p) => c[p]);
32 | }
33 |
34 | handleDownload() {
35 | if (!document) {
36 | if (process.env.NODE_ENV !== 'production') {
37 | console.error('Failed to access document object');
38 | }
39 |
40 | return null;
41 | }
42 |
43 | if (document.getElementById(this.props.table).nodeType !== 1 || document.getElementById(this.props.table).nodeName !== 'TABLE') {
44 | if (process.env.NODE_ENV !== 'production') {
45 | console.error('Provided table property is not html table element');
46 | }
47 |
48 | return null;
49 | }
50 |
51 | const table = document.getElementById(this.props.table).outerHTML;
52 | const sheet = String(this.props.sheet);
53 | const filename = `${String(this.props.filename)}.xls`;
54 |
55 | const uri = 'data:application/vnd.ms-excel;base64,';
56 | const template =
57 | '{table}';
63 |
64 | const context = {
65 | worksheet: sheet || 'Worksheet',
66 | table,
67 | };
68 |
69 | // If IE11
70 | if (window.navigator.msSaveOrOpenBlob) {
71 | const fileData = [
72 | `${''}${table}`,
73 | ];
74 | const blobObject = new Blob(fileData);
75 | document.getElementById('react-html-table-to-excel').click()(() => {
76 | window.navigator.msSaveOrOpenBlob(blobObject, filename);
77 | });
78 |
79 | return true;
80 | }
81 |
82 | const element = window.document.createElement('a');
83 | element.href =
84 | uri +
85 | ReactHTMLTableToExcel.base64(
86 | ReactHTMLTableToExcel.format(template, context),
87 | );
88 | element.download = filename;
89 | document.body.appendChild(element);
90 | element.click();
91 | document.body.removeChild(element);
92 |
93 | return true;
94 | }
95 |
96 | render() {
97 | return (
98 |
106 | );
107 | }
108 | }
109 |
110 | ReactHTMLTableToExcel.propTypes = propTypes;
111 | ReactHTMLTableToExcel.defaultProps = defaultProps;
112 |
113 | export default ReactHTMLTableToExcel;
114 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import chai, { expect } from 'chai';
3 | import chaiEnzyme from 'chai-enzyme';
4 | import { shallow } from 'enzyme';
5 | import sinon from 'sinon';
6 | import ReactHTMLTableToExcel from '../src/ReactHTMLTableToExcel';
7 |
8 | chai.use(chaiEnzyme());
9 |
10 | const props = {
11 | id: 'test-table-xls-button',
12 | className: 'download-table-xls-button',
13 | table: 'table-to-xls',
14 | filename: 'tablexls',
15 | sheet: 'tablexls',
16 | buttonText: 'Download as XLS',
17 | };
18 |
19 | const container = shallow();
20 |
21 | describe('tests for container', () => {
22 | it('should render one button', () => {
23 | expect(container.find('button').length).to.equal(1);
24 | });
25 |
26 | it('should render one button with the correct class applied', () => {
27 | expect(container.find('button').hasClass(props.className)).to.equal(true);
28 | });
29 | });
30 |
--------------------------------------------------------------------------------