概要
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ダウンロードを実現する必要があったので忘れないようにメモしとく