こんにちは、寒い日が続きますね。
久しぶりに更新担当が回ってきました。ふちがみです。
この仕事をしていると、よくあるのが既存システムの管理画面にCSVダウンロード機能を追加する案件です。
そんな時、目の前に表組がHTMLで表示されてるのに、もどかしい。。
という気分になりませんか?私はなります。というか、今現在なっております。
定時も回ったしそろそろ店じまいにしようと考えていたその時。
閃きました。私のゴーストがささやきました。
「そうだ、JavascriptでCSVに変換してファイル保存させればいいじゃない!」
仕様
そうと決まれば善はいそげ。さっそく、やってみましょう。
ざっくりと仕様を考えてみます。こんな感じでしょうか。
[変換]
- Table要素を渡すとCSV文字列に変換する
- TR単位で1行とする
- 改行コードははCRLF
- TR内のTD,TH単位で1列とする
- 文字列はダブルクォートでくくる。
- 文字列にダブルクォート(“)が含まれるときは、(“”)に変換する。
- rowspan, colspan ⇒ 気づかなかったことにします。
[ファイル保存]
- 変換した文字列をファイル保存できるようにする
[その他]
- jQueryはつかわない(なんとなく)
この時点で素直にサーバサイドで実装したほうがいいと思い始めています。
(ほとんど、ビューを変えるだけですし。)
が、気にせず続けたいと思います。
なぜならば、原稿の締め切りが過ぎているからです。
出力対象のテーブル
こんな感じのテーブルをCSV化してみます。
<table id="tbl">
<thead>
<tr>
<th><strong>店舗名</strong></th>
<th>メールアドレス</th>
<th>電話番号</th>
<th>FAX番号</th>
<th>所在地</th>
</tr>
</thead>
<tbody>
<tr>
<td>A店</td>
<td>a@example.com</td>
<td>01-1111-1111</td>
<td>01-1111-0000</td>
<td>東京都新宿区</td>
</tr>
<tr>
<td>"B"店</td>
<td>a@example.com</td>
<td>02-2222-2222,02-2222-2220(採用担当)</td>
<td>02-2222-0000</td>
<td>東京都中野区</td>
</tr>
</tbody>
</table>
実装
せっかちなかたはこちらをご覧下さい。
CSV変換機能の実装
まずはtable要素を渡して、CSV文字列に変換する処理を実装したいと思います。
まずはtableからtr,trからtd th を抽出したいですね。
特定要素の子孫要素から、特定要素を抜き出す処理を作ってみました。
jQueryがあれば、$(‘table’).find(‘tr’) とかで一発なのですが、
ちょっと面倒くさいですね。。
var Util = {
getNodesByName: function(elm /*, string or array*/) {
var children = elm.childNodes;
var nodeNames = ('string' === typeof arguments[1]) ? [arguments[1]] : arguments[1] ;
nodeNames = nodeNames.map(function(str){ return str.toLowerCase() });
var results = [];
for (var i = 0, max = children.length; i < max; i++ ) {
if (nodeNames.indexOf(children[i].nodeName.toLowerCase()) !== -1)
{
results.push(children[i]);
}
else
{
results = results.concat(this.getNodesByName(children[i], nodeNames));
}
}
return results;
}
}
思案の結果、Util.getNodesByNameというメソッドが完成しました。
第1引数にHTMLElementを取り、第2引数に配列または文字列で要素名を取ります。
返ってくるのは、マッチした要素集合です。
入れ子構造が深い場合もあるので、再帰的に探すようにしてみました。
例えばこんな感じで使います。
var tableObj = document.getElementById('テーブルのid');
var rows = Util.getNodesByName(tableObj, 'tr');
var firstRowCols = Util.getNodesByName(rows[0], ['th', 'td']);
このメソッド、trにtrが入れ子になったりするとおそらく子孫をとりこぼすのですが、
その話は聞かなかったことにして下さい。
これを使って前述のtableをCSVに変換していきたいと思います。
var csv = tableToCSV.export(document.getElementById('tbl'))
console.log(csv);
でCSVに変換できるように、tableToCSVを定義しました。
var tableToCSV = {
export: function(elm /*, delimiter */) {
var table = elm;
var rows = this.getRows(table);
var lines = [];
var delimiter = delimiter || ',';
for (var i = 0, numOfRows = rows.length; i < numOfRows; i++) {
var cols = this.getCols(rows[i]);
var line = [];
for (var j = 0, numOfCols = cols.length; j < numOfCols; j++) {
var text = cols[j].textContent || cols[j].innerText;
text = '"'+text.replace(/"/g, '""')+'"';
line.push(text);
}
lines.push(line.join(delimiter));
}
return lines.join("\r\n");
},
getRows: function(elm){
return Util.getNodesByName(elm, 'tr');
},
getCols: function(elm){
return Util.getNodesByName(elm, ['td', 'th']);
}
}
console.logの結果は↓のようになります。
"店舗名","メールアドレス","電話番号","FAX番号","所在地","A店"
"a@example.com","01-1111-1111","01-1111-0000","東京都新宿区"
"""B""店","a@example.com","02-2222-2222,02-2222-2220(採用担当)","02-2222-0000","東京都中野区"
ファイル保存機能の実装
無事、テーブルをカンマ区切りテキストにできたので、
次はファイルとして保存する処理を実装したいと思います。
Chromeでは、download属性にファイル名を指定したaタグで簡単にファイル保存できるそうです。
今回は、Chromeのみ動作するように実装します。
来週の記事担当も私ですので、
クロスブラウザの落としどころは、来週考えることにしたいと思います。
Chromeでは、下記の手順で実現できました。
- 文字列をBlobに変換
- BlobのURLを生成(URL.createObjectURL())
- aタグを生成して生成したURLをhref属性にセット、download属性にファイル名指定
保存処理を追記したtableToCSVは、下記のようになりました。
var tableToCSV = {
export: function(elm /*, delimiter */) {
var table = elm;
var rows = this.getRows(table);
var lines = [];
var delimiter = delimiter || ',';
for (var i = 0, numOfRows = rows.length; i < numOfRows; i++) {
var cols = this.getCols(rows[i]);
var line = [];
for (var j = 0, numOfCols = cols.length; j < numOfCols; j++) {
var text = cols[j].textContent || cols[j].innerText;
text = '"'+text.replace(/"/g, '""')+'"';
line.push(text);
}
lines.push(line.join(delimiter));
}
this.saveAsFile(lines.join("\r\n"));
},
saveAsFile: function(csv) {
var blob = new Blob([csv], {type: 'text/csv'});
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.target = '_blank';
a.download = 'table.csv';
a.click();
},
getRows: function(elm){
return Util.getNodesByName(elm, 'tr');
},
getCols: function(elm){
return Util.getNodesByName(elm, ['td', 'th']);
}
}
結果
今回は、こんなものが出来上がりました。
Chrome以外のブラウザ対応は、次週の記事で実施してみます。
あと、ExcelではUTF8が文字化けすることをすっかり忘れていました。。
文字コード変換ライブラリを提供している方がいるようなので、
次回はそのライブラリを使った文字コード変換も実施します。
最終的なコード(table_csv.html)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<style>
table, th, td {
border: 1px solid;
}
</style>
<script>
var tableToCSV = {
export: function(elm /*, delimiter */) {
var table = elm;
var rows = this.getRows(table);
var lines = [];
var delimiter = delimiter || ',';
for (var i = 0, numOfRows = rows.length; i < numOfRows; i++) {
var cols = this.getCols(rows[i]);
var line = [];
for (var j = 0, numOfCols = cols.length; j < numOfCols; j++) {
var text = cols[j].textContent || cols[j].innerText;
text = '"'+text.replace(/"/g, '""')+'"';
line.push(text);
}
lines.push(line.join(delimiter));
}
this.saveAsFile(lines.join("\r\n"));
},
saveAsFile: function(csv) {
var blob = new Blob([csv], {type: 'text/csv'});
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.target = '_blank';
a.download = 'table.csv';
a.click();
},
getRows: function(elm){
return Util.getNodesByName(elm, 'tr');
},
getCols: function(elm){
return Util.getNodesByName(elm, ['td', 'th']);
}
}
var Util = {
getNodesByName: function(elm /*, string or array*/) {
var children = elm.childNodes;
var nodeNames = ('string' === typeof arguments[1]) ? [arguments[1]] : arguments[1] ;
nodeNames = nodeNames.map(function(str){ return str.toLowerCase() });
var results = [];
for (var i = 0, max = children.length; i < max; i++ ) {
if (nodeNames.indexOf(children[i].nodeName.toLowerCase()) !== -1)
{
results.push(children[i]);
}
else
{
results = results.concat(this.getNodesByName(children[i], nodeNames));
}
}
return results;
}
}
window.onload = function(){
document.getElementById('download').addEventListener('click', function (e){ e.preventDefault(); tableToCSV.export(document.getElementById('tbl')); });
}
</script>
</head>
<body>
<strong style="color:RED;">Google Chromeだけで動作します!!!</strong>
<table id="tbl">
<thead>
<tr>
<th><strong>店舗名</strong></th>
<th>メールアドレス</th>
<th>電話番号</th>
<th>FAX番号</th>
<th>所在地</th>
</tr>
</thead>
<tbody>
<tr>
<td>A店</td>
<td>a@example.com</td>
<td>01-1111-1111</td>
<td>01-1111-0000</td>
<td>東京都新宿区</td>
</tr>
<tr>
<td>"B"店</td>
<td>a@example.com</td>
<td>02-2222-2222,02-2222-2220(採用担当)</td>
<td>02-2222-0000</td>
<td>東京都中野区</td>
</tr>
</tbody>
</table>
<a href="#" id="download">CSVダウンロード</a>
</body>
</html>
コメントを残す