HTML5のtableタグをCSV出力（ダウンロード）してみる

#はじめに

・今回は友人に頼まれ、WEBブラウザ(HTML)で表示されている表(tableタグ)をCSV出力させるHTML & java scriptのコードを書いてみました。あんまり需要があるかわかりませんが、優しい目で見ていただけると幸いです。
・WEBサーバを立てるのは面倒だったのでクライアント側(WEBブラウザ側)のみで処理させています。Chrome、IE、Firefoxにて動作を確認しています。
・ほぼ個人用のメモ&日記的なものですが、よろしければご参照ださい。
#完成物
・入力フィールドの「店舗コード」、「品目」、「金額」に文字、数字を入力し、行を追加ボタンを押すと表に行が追加されます。「CSVファイルダウンロード」のリンクをクリックすると現時点で表示されている表をCSV形式でダウンロードされます。
![Screenshot 2018-05-01 at 12.29.54.png](https://qiita-image-store.s3.amazonaws.com/0/144326/f252a9ed-bb23-cec3-23a0-4ab8a9b0933c.png)

#コード全文
・本来は、CSSとjava scriptは別ファイルに書いておくべきですが、都合上htmlに直書きしました。
・半分以上がCSS（HTMLの見てくれ）ですので、本投稿では、後半のbody属性の中身について解説していきます。
・以下コードは丸コピしてhtmlファイルで保存後、ブラウザで開けば使える状態になっています。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <style type="text/css">
        @charset "UTF-8";
        /* ========BASIC======== */
        html {
        overflow-y:scroll;
        }

        body {
        margin:0;
        padding:0;
        line-height:1.6;
        letter-spacing:1px;
        font-family:Verdana, Helvetica, sans-serif;
        font-size:12px;
        color:#333;
        background:#fff;
        }

        br {
        letter-spacing:normal;
        }

        a {
        color:#0089a1;
        text-decoration:none;
        }

        a:hover {
        color:#0089a1;
        text-decoration:underline;
        }

        img {
        border:0;
        vertical-align:bottom;
        }

        h1,h2,h3,h4,h5,h6 {
        margin:0;
        }


        /* ========TEMPLATE LAYOUT======== */
        #top {
        width:780px;
        margin:10px auto;
        border:1px solid #333;
        }

        #header {
        width:780px;
        }

        #contents {
        clear:both;
        }

        #main {
        float:left;
        width:540px;
        padding:10px;
        }


        /* ========HEADER CUSTOMIZE======== */
        #header h1 {
        margin:0;
        padding:10px;
        font-size:24px;
        }

        #header h1 a {
        color:#333;
        }


        /* ========MAIN CONTENTS CUSTOMIZE======== */
        #main h2 {
        margin-bottom:5px;
        padding:5px 0;
        font-size:16px;
        border-bottom:3px double #ccc;
        }

        #main h3 {
        margin-bottom:5px;
        padding:5px;
        font-size:14px;
        border-left:5px solid #0089a1;
        border-bottom:1px dotted #ccc;
        }

        #main h4 {
        margin-bottom:5px;
        padding:5px;
        font-size:13px;
        color:#fff;
        background:#0089a1;
        }

        #main h5 {
        margin-bottom:5px;
        font-size:13px;
        border-bottom:1px dotted #ccc;
        }

        #main h6 {
        margin-bottom:5px;
        font-size:13px;
        }


        #main p {
        margin:0 0 1em 0;
        }


        /* INFORMATION CUSTOMIZE */
        * html body #main dl.information dd div {
        display:inline-block;
        }

        #main table {
        width:100%;
        border-collapse:collapse;
        }

        #main table th {
        padding:5px;
        font-size:12px;
        text-align:left;
        border:1px solid #aaa;
        background:#f0f7fc;
        }

        #main table td {
        padding:5px;
        font-size:12px;
        text-align:left;
        border:1px solid #aaa;
        }
    </style>

    <title>デモ用アプリ</title>
    </head>
    <body>
        <div id="top">
            <div id="header">
                <h1>hogehoge株式会社</h1>
            </div>
        <div id="contents">
            <div id="main" style="width: 760px;">
              <h2>デモ用アプリ</h2>
              <p>ここにこのサンプルコードの説明を記載してださい。</p>
              <h4>店舗売上データ一覧</h4>
                <table id="table1"  border="1" cellpadding="10">
                    <tr>
                        <th>店舗コード</th>
                        <th>品目</th>
                        <th>金額(円)</th>
                    </tr>
                </table>
                <div style="padding-top: 10px;">
                    <h5>↓入力フィールド</h5>
                    <input name="table1_cell_value" id="th_value1" type="text" placeholder="店舗コード">
                    <input name="table1_cell_value" id="th_value2" type="text" placeholder="品目">
                    <input name="table1_cell_value" id="th_value3" type="text" placeholder="金額(円)">
                    <input type="button" value="行を追加" onclick="add_line()">
                    <b><a id="download" href="#" download="test.csv" onclick="handleDownload()">csvファイルダウンロード</a></b>
                </div>
            </div>
        </div>
        
  <script>
    //ここから表の行追加のコード
    function add_line() {
        for(var i = 0; i < document.getElementsByName("table1_cell_value").length; i++){
            if(document.getElementsByName("table1_cell_value")[i].value ==""){
                alert("未入力項目があります。");
                return false;
            }
        }
        
        var table = document.getElementById('table1');//id=table1という要素を取得
        var row = table.insertRow(-1);//id=table1の中にtrタグを最後の子要素として追加
        var cells = new Array();
        for(var i = 0; i < table.rows[0].cells.length; i++){
            cells[i] = row.insertCell(-1);//新しく作ったrowの中にtrタグを最後の子要素として追加
            cells[i].innerText=document.getElementsByName("table1_cell_value")[i].value;
            document.getElementsByName("table1_cell_value")[i].value="";//入力フィールドの初期化
        }
    }
    //ここまで表の列追加のコード

    //ここからCSV出力＆ダウンロード
    function handleDownload() {
        var bom = new Uint8Array([0xEF, 0xBB, 0xBF]);//文字コードをBOM付きUTF-8に指定
        var table = document.getElementById('table1');//id=table1という要素を取得
        var data_csv="";//ここに文字データとして値を格納していく

        for(var i = 0;  i < table.rows.length; i++){
          for(var j = 0; j < table.rows[i].cells.length; j++){
            data_csv += table.rows[i].cells[j].innerText;//HTML中の表のセル値をdata_csvに格納
            if(j == table.rows[i].cells.length-1) data_csv += "\n";//行終わりに改行コードを追加
            else data_csv += ",";//セル値の区切り文字として,を追加
          }
        }

        var blob = new Blob([ bom, data_csv], { "type" : "text/csv" });//data_csvのデータをcsvとしてダウンロードする関数
        if (window.navigator.msSaveBlob) { //IEの場合の処理
            window.navigator.msSaveBlob(blob, "test.csv"); 
            //window.navigator.msSaveOrOpenBlob(blob, "test.csv");// msSaveOrOpenBlobの場合はファイルを保存せずに開ける
        } else {
            document.getElementById("download").href = window.URL.createObjectURL(blob);
        }

        delete data_csv;//data_csvオブジェクトはもういらないので消去してメモリを開放
    }
    //ここまでCSV出力＆ダウンロード
</script>
  </body>
</html>
```
##bodyタグ内のHTMLの解説（tableタグ編）
・HTMLで表示されている表は以下コードで作成されています。後述のjava scriptによって行を追加ボタンを押されたタイミングでtrタグ、thタグを新たに足していきます。

```html:index.html 
<table id="table1"  border="1" cellpadding="10">
　<tr>
  　<th>店舗コード</th>
    <th>品目</th>
    <th>金額(円)</th>
  </tr>
</table>
```
##bodyタグ内のHTMLの解説（入力フィールド編）
・２〜４行目では「店舗コード」、「品目」、「金額」用の入力フィールドを５，６行目では追加ボタン、CSVダウンロード用リンクを生成しています。
・name属性、id属性は後述のjava scriptで利用するために設定しています。
・add_line()関数、handleDownload()はそれぞれ表の行追加、csvファイルダウンロードを実行するための関数です。解説は後述の「java script　scriptタグ内の解説（add_line()関数編）」、「java script　scriptタグ内の解説（handleDownload()関数編）」で記載します。

```html:index.html
<h5>↓入力フィールド</h5>
<input name="table1_cell_value" id="th_value1" type="text" placeholder="店舗コード">
<input name="table1_cell_value" id="th_value2" type="text" placeholder="品目">
<input name="table1_cell_value" id="th_value3" type="text" placeholder="金額(円)">
<input type="button" value="行を追加" onclick="add_line()">
<b><a id="download" href="#" download="test.csv" onclick="handleDownload()" align="right">csvファイルダウンロード</a></b>
```

##scriptタグ内のjava scriptの解説（add_line()関数編）
・add_line()関数は入力フィールドに入力された文字・数字を表に列を追加する関数です。
・はじめのforループは入力フィールドが空白を防ぐためのコードです。空白の場合はアラートが出ます。
・９行〜１７行目以降は、getElementById()関数でtableタブオブジェクトを取得しからinsertRow()関数、insertCell()を実行し行とセルを追加しています。引数に「-1」を指定することで最後尾に行とセルが追加されます。
・２つ目のforループでは新しくセルを追加したあとそのオブジェクトをcell配列に格納しています。格納後はinnerText属性に入力フィールドの入力値(value属性)を代入させています。

```javascript

    //ここから表の行追加のコード
    function add_line() {
        for(var i = 0; i < document.getElementsByName("table1_cell_value").length; i++){
            if(document.getElementsByName("table1_cell_value")[i].value ==""){
                alert("未入力項目があります。");
                return false;
            }
        }
        
        var table = document.getElementById('table1');//id=table1という要素を取得
        var row = table.insertRow(-1);//id=table1の中にtrタグを最後の子要素として追加
        var cells = new Array();
        for(var i = 0; i < table.rows[0].cells.length; i++){
            cells[i] = row.insertCell(-1);//新しく作ったrowの中にtrタグを最後の子要素として追加
            cells[i].innerText=document.getElementsByName("table1_cell_value")[i].value;
            document.getElementsByName("table1_cell_value")[i].value="";//入力フィールドの初期化
        }
    }
    //ここまで表の列追加のコード
```

##scriptタグ内のjava scriptの解説（handleDownload()関数編）
・詳細はソースコードのコメントアウトに書いてありますが、File APIの元となったBolb関数を用いてcsvファイルをBom付きUTF-8で出力させています、※エクセルで開ける用にするためBom付きにしています。
・forループ内はかなり原始的な処理をさせています。単純にdata_csvにtableタグ中のセルの値(thタグのinnerText属性値)を文字列として加算しています。if else文で「,」や改行コードを挟ませ、結果的にdata_csvに格納される文字列をcsv形式にしています。
※もっとうまい方法を思いつけば良かったのですが。。。。:weary:

```javascript
    //ここからCSV出力＆ダウンロード
    function handleDownload() {
        var bom = new Uint8Array([0xEF, 0xBB, 0xBF]);//文字コードをBOM付きUTF-8に指定
        var table = document.getElementById('table1');//id=table1という要素を取得
        var data_csv="";//ここに文字データとして値を格納していく

        for(var i = 0;  i < table.rows.length; i++){
          for(var j = 0; j < table.rows[i].cells.length; j++){
            data_csv += table.rows[i].cells[j].innerText;//HTML中の表のセル値をdata_csvに格納
            if(j == table.rows[i].cells.length-1) data_csv += "\n";//行終わりに改行コードを追加
            else data_csv += ",";//セル値の区切り文字として,を追加
          }
        }

        var blob = new Blob([ bom, data_csv], { "type" : "text/csv" });//data_csvのデータをcsvとしてダウンロードする関数
        if (window.navigator.msSaveBlob) { //IEの場合の処理
            window.navigator.msSaveBlob(blob, "test.csv"); 
            //window.navigator.msSaveOrOpenBlob(blob, "test.csv");// msSaveOrOpenBlobの場合はファイルを保存せずに開ける
        } else {
            document.getElementById("download").href = window.URL.createObjectURL(blob);
        }

        delete data_csv;//data_csvオブジェクトはもういらないので消去してメモリを開放
    }
    //ここまでCSV出力＆ダウンロード
```
#参考サイト
[・JavaScript のデータを CSV で保存する](http://blog.mudatobunka.org/entry/2017/04/23/135753)
[・JavaScriptからtable要素を制御する方法](https://so-zou.jp/web-app/tech/programming/javascript/dom/node/element/html/table/)
[・javascript で作成したCSVファイルをエクセルで表示可能にする](https://qiita.com/bump_of_kiharu/items/f41beec668e1f3ea675e)
[・Blob と File クラスについて](https://hakuhin.jp/js/file.html)
[・シンプルCSSテンプレート](http://www.coolwebwindow.com/template/simple.php)

#あとがき
今回初めてQiitaに投稿してみましたが、書き方がわからず文字だらけに。。。:sweat_smile:
これからもちょこちょこ書き、ちょこちょこ見やすく分かりやすくしていきます！