Wiita

自分にとってのメモと, プログラミングに関する情報を発信していきます. サイト名の意味は特にありません.

Vue.jsでCSVファイルを読み込む

結構記事があるので余裕だと思ったら躓きまくったので残しておこうと思います.

目指すもの

Image from Gyazo

CSVファイルをドラッグアンドドロップ もしくは 選択してアップロードして, そのCSVファイルを自由に読み取れるようにするまでです.

簡単な流れを説明すると

ファイルアップロード => パース => dataにぶちこむ => 表示

といった感じです.

f:id:ustrgm:20181223003622p:plain

script

vueファイルのCSVを読み込む部分はこんな感じになってます.

<script>
export default {
  data: function() {
    return {
      // CSVファイルのヘッダー部を定義しておきます
      headers: [
        {
          text: "Code",
          align: "left",
          sortable: false,
          value: "code"
        },
        { text: "Name", align: "left", value: "name" },
        { text: "WorkerType", align: "left", value: "workerType" }
      ],
      workers: []
    };
  },
  methods: {
    fileChange: function(e) {
      const file = e.target.files[0];
      const reader = new FileReader();
      const workers = [];

      const loadFunc = () => {
        const lines = reader.result.split("\n");
        lines.forEach(element => {
          const workerData = element.split(",");
          if (workerData.length != 3) return;
          const worker = {
            code: workerData[0],
            name: workerData[1],
            workerType: workerData[2]
          };
          workers.push(worker);
        });
        this.workers = workers;
      };

      // onloadはreadAsBinaryStringでファイルを読み込んだ後に実行されます.
      reader.onload = loadFunc;

      reader.readAsBinaryString(file);
    }
  }
};
</script>

うまくいかないパターン

最初以下のように書いてました

      // onloadはreadAsBinaryStringでファイルを読み込んだ後に実行されます.
      reader.onload =() => {
        const lines = reader.result.split("\n");
        lines.forEach(element => {
          const workerData = element.split(",");
          if (workerData.length != 3) return;
          const worker = {
            code: workerData[0],
            name: workerData[1],
            workerType: workerData[2]
          };
          workers.push(worker);
        });
        this.workers = workers;
      };

これで行けそうな気がしたのですが, this がFileReaderを指すためうまくいきません.

workersとかも更新されません.

上記のように関数を作っておいてそれを渡すようにします.

ちなみにスコープ外の値を操作するのに作成する関数をクロージャといいます.クロージャ

躓いたのはここだけでしたが, ほとんど記事はonloadに直接関数を入れ込んでいるのでthisを使って値を変えようとする場合はうまくいかないです.

また, ローカル変数を作って最後に代入しているのは, 配列のpush関数を使っても再レンダリングがおこらないためです. (値そのものを書き換えるときにsetterが呼ばれ, vueは再レンダリングします)

全体

vuetifyを使ってますがこんな感じにテーブルにして表現することができます.

<template>
  <div>
    <h1>CAVインポート</h1>
    <input @change="fileChange" type="file" id="file_input_expense" name="file_input_expense">
    <v-data-table :headers="headers" :items="workers" class="elevation-1">
      <template slot="items" slot-scope="props">
        <td class="text-xs-right">{{ props.item.code }}</td>
        <td class="text-xs-right">{{ props.item.name }}</td>
        <td class="text-xs-right">{{ props.item.workerType }}</td>
      </template>
    </v-data-table>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      headers: [
        {
          text: "Code",
          align: "left",
          sortable: false,
          value: "code"
        },
        { text: "Name", align: "left", value: "name" },
        { text: "WorkerType", align: "left", value: "workerType" }
      ],
      workers: []
    };
  },
  methods: {
    fileChange: function(e) {
      const file = e.target.files[0];
      const reader = new FileReader();
      const workers = [];

      const loadFunc = () => {
        const lines = reader.result.split("\n");
        lines.forEach(element => {
          const workerData = element.split(",");
          if (workerData.length != 3) return;
          const worker = {
            code: workerData[0],
            name: workerData[1],
            workerType: workerData[2]
          };
          workers.push(worker);
        });
        this.workers = workers;
      };

      reader.onload = loadFunc;

      reader.readAsBinaryString(file);
    }
  }
};
</script>

まとめ

vueというよりほぼJSでしたがこれでCSV読み込んで色々できます!