diff --git a/Makefile b/Makefile index 724fa6f..826c888 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,9 @@ react: ## Simple server for react and clones python -mSimpleHTTPServer .PHONY: next -next: ## next.js demo - # next doesn't support jsx extension +next: init ## next.js demo mkdir -p pages - cp sheetjs.jsx pages/sheetjs.js + cat nexthdr.js sheetjs.jsx > pages/sheetjs.js next .PHONY: native @@ -20,3 +19,9 @@ ios: native ## react-native ios sim .PHONY: android android: native ## react-native android sim cd SheetJS; react-native run-android; cd - + +.PHONY: init +init: ## set up node_modules and symlink + mkdir -p node_modules + cd node_modules; if [ ! -e xlsx ]; then ln -s ../../../ xlsx; fi; cd - + if [ ! -e node_modules/file-saver ]; then npm install file-saver; fi diff --git a/README.md b/README.md index 2afe557..58c0f36 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # React The `xlsx.core.min.js` and `xlsx.full.min.js` scripts are designed to be dropped -into web pages with script tags e.g. +into web pages with script tags: ```html @@ -30,11 +30,7 @@ state in this demo is shaped like the following object: ```js { - cols: [ - { name: "A", key: 0 }, - { name: "B", key: 1 }, - { name: "C", key: 2 }, - ], + cols: [{ name: "A", key: 0 }, { name: "B", key: 1 }, { name: "C", key: 2 }], data: [ [ "id", "name", "value" ], [ 1, "sheetjs", 7262 ] @@ -43,7 +39,32 @@ state in this demo is shaped like the following object: } ``` -The appropriate state model is application-specific. +`sheet_to_json` and `aoa_to_sheet` utility functions can convert between arrays +of arrays and worksheets: + +```js +/* convert from workbook to array of arrays */ +var first_worksheet = workbook.Sheets[workbook.SheetNames[0]]; +var data = XLSX.utils.sheet_to_json(first_worksheet, {header:1}); + +/* convert from array of arrays to workbook */ +var worksheet = XLSX.utils.aoa_to_sheet(data); +var new_workbook = XLSX.utils.book_new(); +XLSX.utils.book_append_sheet(new_workbook, worksheet, "SheetJS"); +``` + +The column objects can be generated with the `encode_col` utility function: + +```js +function make_cols(refstr/*:string*/) { + var o = []; + var range = XLSX.utils.decode_range(refstr); + for(var i = 0; i <= range.e.c; ++i) { + o.push({name: XLSX.utils.encode_col(i), key:i}); + } + return o; +} +``` ## React Native @@ -52,6 +73,7 @@ The appropriate state model is application-specific. Reproducing the full project is straightforward: ```bash +# see native.sh react-native init SheetJS cd SheetJS npm i -S xlsx react react-native react-native-table-component react-native-fs @@ -60,9 +82,44 @@ cp ../react-native.js index.android.js react-native link ``` -This uses `react-native-fs` to read and write files on devices. The app will -prompt before reading and after writing data. The printed location will be: +`react-native-table-component` draws the data table. `react-native-fs` reads +and write files on devices. The app will prompt before reading and after +writing data. The printed location will be: - android: path in the device filesystem - iOS simulator: local path to file - iOS device: a path accessible from iTunes App Documents view + +`react-native-fs` supports `"ascii"` encoding for `readFile` and `writeFile`. +In practice, that encoding uses binary strings compatible with `"binary"` type: + +```js +import { writeFile, readFile } from 'react-native-fs'; + +/* read a workbook */ +readFile(file, 'ascii').then((res) => { + const workbook = XLSX.read(res, {type:'binary'}); + /* DO SOMETHING WITH workbook HERE */ +}); + +/* write a workbook */ +const wbout = XLSX.write(wb, {type:'binary', bookType:"xlsx"}); +writeFile(file, wbout, 'ascii').then((r)=>{/* :) */}).catch((e)=>{/* :( */}); +``` + +## Other Demos + +#### Preact + +`preact-compat` is an easy-to-use compatibility layer that provides equivalents +for `React` and `ReactDOM`. The `preact` demo uses the same JSX component code! +[The docs](https://npm.im/preact-compat#use-without-webpackbrowserify) explain +how to convert the in-browser React demo to Preact. + +#### Server-Rendered React Components with Next.js + +The demo uses the same component code as the in-browser version, but the build +step adds a small header that imports the library. The import is not needed in +deployments that use script tags to include the library. + +[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx) diff --git a/native.sh b/native.sh index 5287ffd..c903cbb 100755 --- a/native.sh +++ b/native.sh @@ -1,5 +1,5 @@ #!/bin/bash - +# xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ if [ ! -e SheetJS ]; then react-native init SheetJS cd SheetJS @@ -13,5 +13,5 @@ fi cp react-native.js SheetJS/index.ios.js cp react-native.js SheetJS/index.android.js cd SheetJS; -react-native link +RNFB_ANDROID_PERMISSIONS=true react-native link cd -; diff --git a/nexthdr.js b/nexthdr.js new file mode 100644 index 0000000..103fdec --- /dev/null +++ b/nexthdr.js @@ -0,0 +1,3 @@ +/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ +import * as XLSX from 'xlsx'; +import { saveAs } from 'file-saver'; diff --git a/pages/index.js b/pages/index.js index f7eac77..6935cd9 100644 --- a/pages/index.js +++ b/pages/index.js @@ -6,16 +6,11 @@ export default () => ( SheetJS React Demo - - - - - -
+

SheetJS React Demo


Source Code Repo
diff --git a/react-native.js b/react-native.js index fff0f68..682ad7d 100644 --- a/react-native.js +++ b/react-native.js @@ -1,13 +1,24 @@ /* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ - import * as XLSX from 'xlsx'; import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View, Button, Alert, Image } from 'react-native'; import { Table, Row, Rows } from 'react-native-table-component'; -import { writeFile, readFile, DocumentDirectoryPath } from 'react-native-fs' +// react-native-fs +import { writeFile, readFile, DocumentDirectoryPath } from 'react-native-fs'; const DDP = DocumentDirectoryPath + "/"; +const input = res => res; +const output = str => str; + +// react-native-fetch-blob +/* +import RNFetchBlob from 'react-native-fetch-blob'; +const { writeFile, readFile, dirs:{ DocumentDir } } = RNFetchBlob.fs; +const DDP = DocumentDir + "/"; +const input = res => res.map(x => String.fromCharCode(x)).join(""); +const output = str => str.split("").map(x => x.charCodeAt(0)); +*/ const make_cols = refstr => Array.from({length: XLSX.utils.decode_range(refstr).e.c + 1}, (x,i) => XLSX.utils.encode_col(i)); @@ -26,22 +37,32 @@ export default class SheetJS extends Component { {text: 'Cancel', onPress: () => {}, style: 'cancel' }, {text: 'Import', onPress: () => { readFile(DDP + "sheetjs.xlsx", 'ascii').then((res) => { - const wb = XLSX.read(res, {type:'binary'}); + /* parse file */ + const wb = XLSX.read(input(res), {type:'binary'}); + + /* convert first worksheet to AOA */ const wsname = wb.SheetNames[0]; const ws = wb.Sheets[wsname]; const data = XLSX.utils.sheet_to_json(ws, {header:1}); + + /* update state */ this.setState({ data: data, cols: make_cols(ws['!ref']) }); }).catch((err) => { Alert.alert("importFile Error", "Error " + err.message); }); }} ]); } exportFile() { + /* convert AOA back to worksheet */ const ws = XLSX.utils.aoa_to_sheet(this.state.data); + + /* build new workbook */ const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "SheetJS"); - const wbout = XLSX.write(wb, {type:"binary", bookType:"xlsx"}); + + /* write file */ + const wbout = XLSX.write(wb, {type:'binary', bookType:"xlsx"}); const file = DDP + "sheetjsw.xlsx"; - writeFile(file, wbout, 'ascii').then((res) =>{ + writeFile(file, output(wbout), 'ascii').then((res) =>{ Alert.alert("exportFile success", "Exported to " + file); }).catch((err) => { Alert.alert("exportFile Error", "Error " + err.message); }); }; diff --git a/sheetjs.jsx b/sheetjs.jsx index bc3fc55..f5c5bc1 100644 --- a/sheetjs.jsx +++ b/sheetjs.jsx @@ -1,95 +1,16 @@ /* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -const SheetJSFT = [ - "xlsx", "xlsb", "xlsm", "xls", "xml", "csv", "txt", "ods", "fods", "uos", "sylk", "dif", "dbf", "prn", "qpw", "123", "wb*", "wq*", "html", "htm" -].map(function(x) { return "." + x; }).join(","); - -/* - Simple HTML5 file drag-and-drop wrapper - usage: ... - handleFile(file:File):void; +/* Notes: + - usage: `ReactDOM.render( , document.getElementById('app') );` + - xlsx.full.min.js is loaded in the head of the HTML page + - this script should be referenced with type="text/babel" + - babel.js in-browser transpiler should be loaded before this script */ -class DragDropFile extends React.Component { - constructor(props) { - super(props); - this.onDrop = this.onDrop.bind(this); - }; - suppress(evt) { evt.stopPropagation(); evt.preventDefault(); }; - onDrop(evt) { evt.stopPropagation(); evt.preventDefault(); - const files = evt.dataTransfer.files; - if(files && files[0]) this.props.handleFile(files[0]); - }; - render() { return ( -
- {this.props.children} -
- ); }; -}; - -/* - Simple HTML5 file input wrapper - usage: - handleFile(file:File):void; -*/ -class DataInput extends React.Component { - constructor(props) { - super(props); - this.handleChange = this.handleChange.bind(this); - }; - handleChange(e) { - const files = e.target.files; - if(files && files[0]) this.props.handleFile(files[0]); - }; - render() { return ( -
-
- - -
-
- ); }; -} - -/* generate an array of column objects */ -const make_cols = refstr => Array(XLSX.utils.decode_range(refstr).e.c + 1).fill(0).map((x,i) => ({name:XLSX.utils.encode_col(i), key:i})); - -/* - Simple HTML Table - usage: - data:Array >; - cols:Array<{name:string, key:number|string}>; -*/ -class OutTable extends React.Component { - constructor(props) { super(props); }; - render() { return ( -
- - - {this.props.cols.map((c) => )} - - - {this.props.data.map(r => - {this.props.cols.map(c => )} - )} - -
{c.name}
{ r[c.key] }
-
- ); }; -}; - -/* see Browser download file example in docs */ -function s2ab(s) { - const buf = new ArrayBuffer(s.length); - const view = new Uint8Array(buf); - for (let i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; - return buf; -} - class SheetJSApp extends React.Component { constructor(props) { super(props); this.state = { data: [], /* Array of Arrays e.g. [["a","b"],[1,2]] */ - cols: [] /* Array of column objects e.g. { name: "C", key: 2 } */ + cols: [] /* Array of column objects e.g. { name: "C", K: 2 } */ }; this.handleFile = this.handleFile.bind(this); this.exportFile = this.exportFile.bind(this); @@ -137,3 +58,91 @@ class SheetJSApp extends React.Component { }; if(typeof module !== 'undefined') module.exports = SheetJSApp + +/* -------------------------------------------------------------------------- */ + +/* + Simple HTML5 file drag-and-drop wrapper + usage: ... + handleFile(file:File):void; +*/ +class DragDropFile extends React.Component { + constructor(props) { + super(props); + this.onDrop = this.onDrop.bind(this); + }; + suppress(evt) { evt.stopPropagation(); evt.preventDefault(); }; + onDrop(evt) { evt.stopPropagation(); evt.preventDefault(); + const files = evt.dataTransfer.files; + if(files && files[0]) this.props.handleFile(files[0]); + }; + render() { return ( +
+ {this.props.children} +
+ ); }; +}; + +/* + Simple HTML5 file input wrapper + usage: + handleFile(file:File):void; +*/ +class DataInput extends React.Component { + constructor(props) { + super(props); + this.handleChange = this.handleChange.bind(this); + }; + handleChange(e) { + const files = e.target.files; + if(files && files[0]) this.props.handleFile(files[0]); + }; + render() { return ( +
+
+ + +
+
+ ); }; +} + +/* + Simple HTML Table + usage: + data:Array >; + cols:Array<{name:string, key:number|string}>; +*/ +class OutTable extends React.Component { + constructor(props) { super(props); }; + render() { return ( +
+ + + {this.props.cols.map((c) => )} + + + {this.props.data.map((r,i) => + {this.props.cols.map(c => )} + )} + +
{c.name}
{ r[c.key] }
+
+ ); }; +}; + +/* list of supported file types */ +const SheetJSFT = [ + "xlsx", "xlsb", "xlsm", "xls", "xml", "csv", "txt", "ods", "fods", "uos", "sylk", "dif", "dbf", "prn", "qpw", "123", "wb*", "wq*", "html", "htm" +].map(function(x) { return "." + x; }).join(","); + +/* see Browser download file example in docs */ +function s2ab(s/*:string*/)/*:ArrayBuffer*/ { + const buf = new ArrayBuffer(s.length); + const view = new Uint8Array(buf); + for (let i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; + return buf; +} + +/* generate an array of column objects */ +const make_cols = refstr => Array(XLSX.utils.decode_range(refstr).e.c + 1).fill(0).map((x,i) => ({name:XLSX.utils.encode_col(i), key:i}));