Skip to content

Instantly share code, notes, and snippets.

@valex
Created October 22, 2018 16:15
Show Gist options
  • Save valex/319b70b78d083563e4ae2cba3a4cbe3b to your computer and use it in GitHub Desktop.
Save valex/319b70b78d083563e4ae2cba3a4cbe3b to your computer and use it in GitHub Desktop.
Table export to JSON and CSV
<!DOCTYPE html>
<html>
<head>
<title>Table</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="03.00.table.css">
</head>
<body>
<div id="app">
<!-- my app renders here -->
</div>
<script charset="utf-8" crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.2/prop-types.min.js"></script>
<script type="text/babel">
class Excel extends React.Component{
constructor(params) {
super(params);
this.displayName= 'Excel';
this._preSearchData= null;
this.state = {
data: this.props.initialData,
sortby: null,
descending: false,
edit: null, // [row index, cell index]
search: false,
};
// This binding is necessary to make `this` work in the callback
this._save = this._save.bind(this);
this._search = this._search.bind(this);
this._sort = this._sort.bind(this);
this._showEditor = this._showEditor.bind(this);
this._toggleSearch = this._toggleSearch.bind(this);
};
_download(format, ev) {
var contents = format === 'json'
? JSON.stringify(this.state.data)
: this.state.data.reduce(function(result, row) {
return result
+ row.reduce(function(rowresult, cell, idx) {
return rowresult
+ '"'
+ cell.replace(/"/g, '""')
+ '"'
+ (idx < row.length - 1 ? ',' : '');
}, '')
+ "\n";
}, '');
var URL = window.URL || window.webkitURL;
var blob = new Blob([contents], {type: 'text/' + format});
ev.target.href = URL.createObjectURL(blob);
ev.target.download = 'data.' + format;
};
_search(e) {
var needle = e.target.value.toLowerCase();
if (!needle) {
this.setState({data: this._preSearchData});
return;
}
var idx = e.target.dataset.idx;
var searchdata = this._preSearchData.filter(function(row) {
return row[idx].toString().toLowerCase().indexOf(needle) > -1;
});
this.setState({data: searchdata});
};
_toggleSearch(e){
if (this.state.search) {
this.setState({
data: this._preSearchData,
search: false,
});
this._preSearchData = null;
} else {
this._preSearchData = this.state.data;
this.setState({
search: true,
});
}
};
_save(e){
e.preventDefault();
var input = e.target.firstChild;
var data = this.state.data.slice();
data[this.state.edit.row][this.state.edit.cell] = input.value;
this.setState({
edit: null,
data: data,
});
};
_sort(e){
var column = e.target.cellIndex;
var data = this.state.data.slice(); // copy data
var descending = this.state.sortby === column && !this.state.descending;
data.sort(function(a, b) {
return descending
? (a[column] < b[column] ? 1 : -1)
: (a[column] > b[column] ? 1 : -1);
});
this.setState({
data: data,
sortby: column,
descending: descending,
});
};
_showEditor(e){
this.setState({edit: {
row: parseInt(e.target.dataset.row, 10),
cell: e.target.cellIndex,
}});
};
_renderSearch(){
if (!this.state.search) {
return null;
}
return (
<tr onChange={this._search}>
{
this.props.headers.map(function(_ignore, idx)
{
return (<td key={idx}><input
style={{width: 100+'%'}}
data-idx={idx} type="text"/></td>);
}, this)
}
</tr>
);
};
_renderToolbar() {
return(<div className="toolbar">
<button onClick={this._toggleSearch}>Search</button>
<a onClick={this._download.bind(this, 'json')} href='data.json'>Export JSON</a>
<a onClick={this._download.bind(this, 'csv')} href='data.csv'>Export CSV</a>
</div>);
};
_renderTable() {
return (
<table>
<thead onClick={this._sort}>
<tr>
{
this.props.headers.map(function(title, idx)
{
if (this.state.sortby === idx) {
title += this.state.descending ? ' \u2191' : ' \u2193'
}
return (<th key={idx}>{title}</th>);
}, this)
}
</tr>
</thead>
<tbody onDoubleClick={this._showEditor}>
{this._renderSearch()}
{
this.state.data.map(function(row, idrow)
{
return (
<tr key={idrow}>
{
row.map(function(cell, idcell){
var content = cell;
var edit = this.state.edit;
if (edit && edit.row === idrow && edit.cell === idcell) {
content = <form onSubmit={this._save}><input defaultValue={cell} type="text"/></form>;
}
return (<td data-row={idrow} key={idcell}>{content}</td>);
}, this)
}
</tr>
);
}, this)
}
</tbody>
</table>
);
};
render(){
return (
<div>
{this._renderToolbar()}
{this._renderTable()}
</div>
);
};
}
Excel.propTypes = {
headers: PropTypes.arrayOf(PropTypes.string),
initialData: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
};
var headers = [
"Book", "Author", "Language", "Published", "Sales"
];
var data = [
["The Lord of the Rings", "J. R. R. Tolkien", "English", "1954–1955", "150 million"],
["Le Petit Prince (The Little Prince)", "Antoine de Saint-Exupéry", "French", "1943", "140 million"],
["Harry Potter and the Philosopher's Stone", "J. K. Rowling", "English", "1997", "107 million"],
["And Then There Were None", "Agatha Christie", "English", "1939", "100 million"],
["Dream of the Red Chamber", "Cao Xueqin", "Chinese", "1754–1791", "100 million"],
["The Hobbit", "J. R. R. Tolkien", "English", "1937", "100 million"],
["She: A History of Adventure", "H. Rider Haggard", "English", "1887", "100 million"],
];
ReactDOM.render(
React.createElement(Excel, {
headers: headers,
initialData: data,
}),
document.getElementById("app")
);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment