Help us understand the problem. What is going on with this article?

サーバーで生成した複数のファイルをjs + ajaxで連続ダウンロードさせる(Chrome)

業務システムってエクセルが好きだよね・・・

こんにちは。
今日も今日とて業務システム開発。筆者です。

いきなりですけど、業務システムってエクセル好きですよね。というか。皆さんエクセル好きですよね。:cry:
もちろんCSVとかを生成して結果を返すという動きをすることもあるのですが、Rails側でユーザーの入力値からエクセルファイルに文字列を入力し、その結果のエクセルを返す。(CSVはSQLの実行結果、エクセルは帳票等の出力みたいに使い分けたりしてます)

とかいう処理をすることがあります。
筆者はよく,Rubyでエクセルを編集・作成するときに、RubyXlというgemを利用させていただくのですが、今回はエクセルを作成した後の話で、

どうやってユーザーに生成したエクセルファイルをダウンロードさせるかという内容の記事になります。

今回の前提条件

  • エクセルファイルはユーザーの入力値により、サーバー側で生成
  • ajaxを利用した非同期通信であり、Railsのsend_fileは使えない。
  • エクセルは複数個生成される可能性があり、それをzip等で固めず個別にダウンロードさせる必要がある
  • ブラウザはGoogle Chrome
  • エクセル生成後保存先のファイルパスをajaxでjs側に返し、そのパスにアクセスしてダウンロード処理を走らせるような流れ?

というところでやってみました。

失敗例

まずは、ダメな例からです。
シンプルに「パスが分かってるから、window.location.href = "ファイルパス"」でええやん!と思って失敗したパターン。

$.ajax({
        //ここでサーバーへリクエストを送信
      }).done(function(json) {
        //サーバーから帰ってきたファイルパス群(配列)を受け取って一回一回ダウンロードしようとして失敗
        var downloadUrls = json.path;
        downloadUrls.forEach(function (value, idx) {
            const response = {
              file: value,
            };
            setTimeout(() => {
                window.location.href = response.file;
            }, idx * 50)
        })
      }).fail(function(err){
          console.log('へへ・・・悪い・・・失敗しちまったよ');
      })

失敗というか,Chorme側のセキュリティ設定?でエラーメッセーが表示されます。
一度無理やりwindow.location.hrefでダウンロードさせておけば、しばらく動作させることはできましたが、そもそもワーニングメッセージ出てるし、セキュリティ的にどうなんだっていう話なので却下。

成功例(とりあえず)

調べれば、調べるほどwindow.location.hrefでは難しいかなということが分かってきました。
(だってそんなのホイホイ許していたら、ウィルスとか簡単にダウンロードさせてしまえるものね:cry:

どうも、色々と調べていると、
chromeでファイルをダウンロードさせるときには、donwload属性なるものを付与したaタグでダウンロードしてもらえれば良いみたい。

今回はダウンロード処理は自動で行う仕様であるため、ユーザー様にダウンロードボタンをクリックさせるなどいうことはして貰うわけには行きませんので、次のように対応しました。

$.ajax({
     //ここでサーバー側にリクエストを送る
    }).done(function(json) {
      //サーバー側からもらったファイルパスを利用して、一時的に<a>タグを生成し、クリックイベントを起こした後、aタグを除去
      var downloadUrls = json.path;//['url1','url2']みたいに結果が帰ってきている
      downloadUrls.forEach(function (value, idx) {
          const response = {
            file_path: value,
          };
          setTimeout(() => {
              //aタグを生成
              const a = document.createElement('a');
              //href属性にファイルパスを生成
              a.href = response.file_path;
              //download属性にファイル名を付与。
              a.download = 'ファイル名';
              //生成したaタグをクリックするイベント
              a.click();
              //用無しのaタグ君には消えてもらうかな!
              document.body.removeChild(a);
          }, idx * 50)
      })
    }).fail(function(err){
        console.log('失敗じゃない。諦めたときが失敗なんだ。');
    })

こいつ、動くぞ!ってなもんでとりあえず期待した動作をしてくれるようになりました。

zipで固めて返した方が楽だなぁと思いましたが、却下されたのでとりあえずこれで動かしてみました。
当たり前ですが、セキュリティの問題があるので、どのような方法が一番良いのかなぁ・・・と悩む日々です。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away