├── .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 | [![Build Status](https://travis-ci.org/zsusac/ReactHTMLTableToExcel.svg?branch=master)](https://travis-ci.org/zsusac/ReactHTMLTableToExcel) 5 | 6 | [![NPM](https://nodei.co/npm/react-html-table-to-excel.png)](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 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
FirstnameLastnameAge
JillSmith50
EveJackson94
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 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
FirstnameLastnameAge
JillSmith50
EveJackson94
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 | --------------------------------------------------------------------------------