CSV や Excel 等のエクスポート処理の終了を検知して JavaScript で何かする方法

## はじめに

Web アプリでは CSV や Excel、PDF をエクスポートする機能を作ることがそれなりにあるかと思います。その処理の終了後、つまり生成した CSV や Excel データがレスポンスされた時やその後に JavaScript で何らかの処理を実行する簡単な方法を見つけたのでメモしておきます。

> [ファイルダウンロード完了後に画面遷移などをjavascriptで行う](http://d.hatena.ne.jp/nabehiro/20140208/1391850498) で紹介されている方法を参考にさせてもらっています。

## アプリの事前準備

何かのデータを CSV としてエクスポートする以下のような機能が Rails アプリにあるとします（バックエンドは Ruby/Rails である必要はありません。なんであれ基本やることは同じです）

```rb:app/controllers/foo_controller.rb
class FooController < ApplicationController
  def export
    csv_data = CSV.generate do |csv|
      # （CSV データを作る処理）
    end

    send_data csv_data, disposition: 'attachment',
                        type: 'text/csv',
                        filename: 'foo.csv'
  end
end
```

> 補足. [Railsにて検索結果をそのままCSV出力する（やや手抜きで）](http://www.ohmyenter.com/?p=481) 

続いて、ビューは以下のような感じです。それっぽくするために、抽出条件として年度を指定できる風にしています。

```erb:app/views/foo/index.html.erb
<h1>エクスポート</h1>

<%= form_tag foo_export_path do %>
  <p>
    <%= label_tag 'year', '出力年度:' %>
    <%= text_field_tag 'year', 2014 %>
  </p>
  <p>
    <%= submit_tag '実行' %>
  </p>
<% end %>
```

画面上の "実行ボタン" をクリックすると `foo.csv` という名前の CSV ファイルがダウンロードされます。

以上でアプリの事前準備は完了です。

## 本題

では、この機能を以下のようにカスタマイズしてみます。

  * 処理中は "実行" ボタンを使用不可へ
  * 処理終了後に完了メッセージを表示し、 "実行" ボタンを使用可へ

これらを実現するために、アプリを以下のように修正します。

まず、エクスポート処理が終了するときに `exported` という名前で Cookie を作成します。

```rb:app/controllers/foo_controller.rb
class FooController < ApplicationController
  def export
    csv_data = CSV.generate do |csv|
      # （CSV データを作る処理）
    end

    # exported という名前で cookie を作成
    cookies[:exported] = { value: 'yes', expires: 1.minutes.from_now  }

    send_data csv_data, disposition: 'attachment',
                        type: 'text/csv',
                        filename: 'foo.csv'
  end
end
```

そして、ビューを以下のようにカスタマイズします。

```erb:app/views/foos/index.html.erb
<h1>エクスポート</h1>

<div id="message"></div>

<%= form_tag foo_export_path, id: 'form' do %>
  <p>
    <%= label_tag 'year', '出力年度:' %>
    <%= text_field_tag 'year', 2014 %>
  </p>
  <p><%= submit_tag '実行', id: 'button' %></p>
<% end %>

<%# Cookie を扱いやすくするために jquery.cookie.js を使う %>
<%= javascript_include_tag 'jquery.cookie.min.js' %>

<script>
  $('#form').submit(function(e) {
    // 実行ボタンをロックし、ラベルを "処理中..." へ
    $('#button').prop('disabled', true).val('処理中...');

    var intervalId;

    // 1 秒間隔で exported cookie をチェック
    intervalId = setInterval(function() {
      if ($.cookie('exported')) {
        // 実行ボタンをアンロック後ラベルを元に戻す
        $('#button').prop('disabled', false).val('実行');

        // 処理が正常に完了した旨のメッセージを表示
        $('#message').text('正常に終了しました。');

        // ポーリングを停止
        clearInterval(intervalId);
        // フラグをクリア
        $.removeCookie('exported', { path: '/' });
      }
    }, 1000);
  });
</script>
```

以上です。
流れを簡単に説明すると以下のようになります。

  1. (front) 実行ボタンをクリック
  2. (front) 実行ボタンがロックされ、ラベルも "処理中..." へ
  3. (front) exporeted cookie の有無をチェックする処理を 1 秒間隔でポーリング
  3. (rails) エクスポート処理が終了後 exported cookie を作成
  4. (rails) CSV データのレスポンスとともに cookie もクライアントへ送出
  5. (front) クライアント側exproted cookie が見つかったら↓
  6. (front) 実行ボタンをアンロックし、ラベルも元に戻し、メッセージを画面上に表示
  7. (front) exported cookie を削除

その他、処理中のインジケータを表示させたり、完了後にどこかにリダイレクトさせるなど、工夫次第でいろいろできそうです。

### サンプルアプリ

動作確認用として GitHub にサンプルアプリを公開しています。
https://github.com/hidakatsuya/sample-run-any-js-after-download-processing

このアプリは [ThinReports](http://www.thinreports.org) の [Rails4 サンプルアプリ](https://github.com/thinreports/thinreports-rails4-example) をベースとしています。セットアップ方法は [README.md](https://github.com/hidakatsuya/sample-run-any-js-after-download-processing/blob/master/README.md) を参考にして下さい。

実行後、 `http://localhost:3000/tasks` へ移動し、 `Print Tasks` をクリックすると一覧表の印刷処理が開始します。開始と同時に `Print Tasks` のラベルが `Now Printing...` へ変化し、処理完了後に PDF がダウンロードされると同時にラベルが `Done!!` へ変わります。このラベルの変化をこの記事で紹介した方法で実現しています。

フォーク元の Rails4 Example に対する変更箇所は、この [コミット](https://github.com/hidakatsuya/sample-run-any-js-after-download-processing/commit/dec3f6f9980f48df07ff86770e6deaa0c6ce26a7) をご覧下さい。

### 参考

* [ファイルダウンロード完了後に画面遷移などをjavascriptで行う](http://d.hatena.ne.jp/nabehiro/20140208/1391850498)
* [jquery.cookie](https://github.com/carhartl/jquery-cookie)