LoginSignup
5

More than 5 years have passed since last update.

ajax(XMLHttpRequest)でsjisのcsvダウンロード

Posted at

概要

ajax(XMLHttpRequest)でtext/csv形式のapiを呼び出し
取得したcsvをブラウザでダウンロードする方法。
特にExcelで開いたら文字化けせずsjis形式で表示できることがポイント

ついでにプログラム内でcsvを生成して出力するケースを書いとく。

apiから取得したcsvをダウンロード

ポイントは以下2点

  • ポイント1
    • xhr.responseType = "blob"でレスポンスをバイナリ形式で受け取る
  • ポイント2
    • aタグを生成して取得したバイナリデータを埋め込む
/** Blob取得APIのレスポンス */
export default interface ApiBlobResponse {
  /** HTTPステータスコード */
  status: number;
  /** HTTPステータス名 */
  stausText: string;
  /** レスポンスデータ */
  response: Blob;
  /** レスポンスヘッダ */
  headers: {
    [name: string]: string;
  } & { "content-disposition"?: string; };
}

/**
 * CSVをダウンロード
 * @param result csv取得APIの結果(バイナリデータ)
 * @param fileName csvファイル名の指定
 */
export function downloadCsv(result: ApiBlobResponse | undefined): void {
    if (result === undefined || result.headers["content-disposition"] === undefined) {
        return;
    }
    const a = document.createElement("a");
    document.body.appendChild(a);
    a.href = window.URL.createObjectURL(result.response);  // ★ポイント2
    const matchedFileName: string[] | null = result.headers["content-disposition"].match(/.*filename=(.*\.csv)/);
    if (matchedFileName === null) {
        return;
    }
    a.download = matchedFileName[1];
    a.click();
}

/**
 * ajax (csv取得用)
 * @param url リクエストURL
 */
export function ajaxCsv<T>(url: string, method: string, data?: T): Promise<ApiBlobResponse> {
    return new Promise<ApiBlobResponse>
        ((resolve: (result: ApiBlobResponse) => void, reject: (result: ApiBlobResponse) => void) => {
            // XMLHttpRequest
            const xhr: XMLHttpRequest = new XMLHttpRequest();
            xhr.open(method, url);
            xhr.responseType = "blob";  // ★ポイント1

            // レスポンスの処理
            // 成功時
            xhr.onload = (() => {
                const response = getBlobResponseInfo(xhr);
                if (xhr.status === 200) {
                    resolve(response);
                } else {
                    reject(response);
                }
            });
            // 失敗時
            xhr.onerror = ((error: ProgressEvent) => {
                const result: ApiBlobResponse = {
                    headers: {},
                    response: new Blob(),
                    status: 500,
                    stausText: error.type,
                };
                reject(result);
            });

            // リクエスト実施
            if (method === "GET") {
                // GET
                xhr.send();
            } else {
                // POST --> POSTデータ(JSON)
                xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
                if (data === undefined || data === null) {
                    xhr.send();
                } else {
                    xhr.send(JSON.stringify(data));
                }
            }
        });
}

/**
 * Blob型のレスポンス詳細情報を取得する
 * @param xhr XMLHttpRequest
 * @return レスポンス詳細情報
 */
export function getBlobResponseInfo(xhr: XMLHttpRequest): ApiBlobResponse {
    const headers: Params = {};
    const responseHeader: string[] = xhr.getAllResponseHeaders().split("\n");
    responseHeader.forEach((line: string) => {
        const header: string[] = line.trim().split(":");
        if (header.length === 2) {
            headers[header[0].trim()] = header[1].trim();
        }
    });
    const apiResponseInfo: ApiBlobResponse = {
        headers,
        response: xhr.response,
        status: xhr.status,
        stausText: xhr.statusText,
    };
    return apiResponseInfo;
}

apiから取得した文字列のcsvをダウンロード

最初文字列で取得したcsvをなんとかダウンロードしようと苦労してた頃の残骸。
だが、プログラム内でcsvを文字列生成した際などは使えそうなのでメモしておく。

一応以下2つのパッケージをインストールしてる

  • npm install --save-dev encoding-japanese
  • npm install --save-dev @types/encoding-japanese
import Encoding from "encoding-japanese";

export default interface ApiResponse {
  /** HTTPステータスコード */
  status: number;
  /** HTTPステータス名 */
  stausText: string;
  /** レスポンスデータ */
  responseText: string;
  /** レスポンスヘッダ */
  headers: {
    [name: string]: string;
  }
}

/**
 * CSVをダウンロード
 * @param result csv取得APIの結果
 * @param fileName csvファイル名の指定
 */
export function downloadCsv(result: string | null | undefined, fileName: string): void {
    if (result === undefined || result === null) {
        return;
    }
    const a = document.createElement("a");
    document.body.appendChild(a);
    // ★ポイント2 この辺以下
    const unicodeList = [];
    for (let i = 0; i < result.length; i += 1) {
        unicodeList.push(result.charCodeAt(i));
    }
    // 変換処理の実施
    const shiftJisCodeList = Encoding.convert(unicodeList, "SJIS", "UNICODE");
    const uInt8List = new Uint8Array(shiftJisCodeList);

    const blob = new Blob([uInt8List], { type: "text/csv" });
    a.href = window.URL.createObjectURL(blob);
    a.download = fileName;
    a.click();
}

/**
 * ajax (csv取得用)
 * @param url リクエストURL
 */
export function ajaxCsv<T>(url: string, method: string, data?: T): Promise<string | null> {
    return new Promise<string | null>
        ((resolve: (result: string | null) => void, reject: (result: ApiResponse) => void) => {
            // XMLHttpRequest
            const xhr: XMLHttpRequest = new XMLHttpRequest();
            xhr.open(method, url);
            xhr.overrideMimeType("text/csv;charset=Shift-JIS"); // ★ポイント1

            // レスポンスの処理
            // 成功時
            xhr.onload = (() => {
                const response: string | null = (xhr.responseText === "") ? null : xhr.responseText;
                if (xhr.status === 200) {
                    resolve(response);
                } else {
                    reject(getResponseInfo(xhr));
                }
            });
            // 失敗時
            xhr.onerror = ((error: ProgressEvent) => {
                // ProgressEvent からはほとんどエラー情報が取得できない
                const result: ApiResponse = {
                    headers: {},
                    responseText: error.type,
                    status: 500,
                    stausText: error.type,
                };
                reject(result);
            });

            // リクエスト実施
            if (method === "GET") {
                // GET
                xhr.send();
            } else {
                // POST --> POSTデータ(JSON)
                xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
                if (data === undefined || data === null) {
                    xhr.send();
                } else {
                    xhr.send(JSON.stringify(data));
                }
            }
        });
}

まとめ

そもそもAPIレスポンスを受けて、別で用意した対象csvファイルのURLにアクセスさせる!
とかの方が最適なのかもしれないが、たまたまこういうcsvダウンロードを実現する必要があったので忘れないようにメモしとく

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5