One IT Thing

IT業界を楽しむ為の学習系雑記

html5

IndexedDBにblob保存されたPDFファイルを外部アプリに頼らずにJavascriptで表示(前編)

投稿日:2019年6月10日 更新日:

はじめに

PDFをHTTPダウンロードするとHDDに保存されるか、外部ビューワが起動するかの2択になります。スマホだとHDDに保存されると表示が面倒ですし、外部ビューワが入っていないAndroidなんかだとインストールするかGoogleドキュメントに上げるまで詰みます。

開発しているアプリでのダウンロードの場合は尚更アプリで管理してよ、みたいな圧力が掛かるので、ダウンロードしたらIndexedDBに保存(前半)、PDF.jsで表示(後半)する方法を検証します。

前半は本題の後編に入る前の準備作業になります。

検証環境

検証プロジェクトはionicでサクッと作成していきます。 Cordovaは無しです。

  • Windows 10
  • NodeJS 8.11
  • Ionic 4.1.0
  • Angular 7.2.2

Ionicプロジェクト作成

ionic-cli@5.0.1が入りました。

C:\src\ionic>npm install -g ionic
C:\Program Files\nodejs\ionic -> C:\Program Files\nodejs\node_modules\ionic\bin\ionic
+ ionic@5.0.1
added 140 packages, removed 253 packages, updated 24 packages and moved 2 packages in 69.815s

「pdfdl」という名前でブランクプロジェクトを作成します。

C:\src\ionic>ionic start pdfdl blank
√ Preparing directory .\pdfdl - done!
(node:8004) ExperimentalWarning: The http2 module is an experimental API.
√ Downloading and extracting blank starter - done!

Installing dependencies may take several minutes.

  ──────────────────────────────────────────────────────────────────────────────

         Ionic Advisory, tailored solutions and expert services by Ionic

                             Go to market faster
                    Real-time troubleshooting and guidance
        Custom training, best practices, code and architecture reviews
      Customized strategies for every phase of the development lifecycle

               Learn more: https://ion.link/advisory

  ──────────────────────────────────────────────────────────────────────────────


> npm.cmd i
npm WARN notice [SECURITY] open has the following vulnerability: 1 critical. Go here for more details: https://www.npmjs.com/advisories?search=open&version=6.0.0 - Run `npm i npm@latest -g` to upgrade your npm version, and then `npm audit` to get more info.

    (snip)

出来たプロジェクトにスマホからもアクセスできるよう–address 0.0.0.0でHTTPサーバを起動します。(最近のionic serveはデフォルトlocalhost起動になりました)

C:\src\ionic\pdfdl>ionic serve --address 0.0.0.0
> ng.cmd run app:serve --host=0.0.0.0 --port=8100
[ng] WARNING: This is a simple server for use in testing or debugging Angular applications
[ng] locally. It hasn't been reviewed for security issues.
[ng] Binding this server to an open connection can result in compromising your application or
[ng] computer. Using a different host than the one passed to the "--host" flag might result in
[ng] websocket connection issues. You might need to use "--disableHostCheck" if that's the
[ng] case.
[INFO] Waiting for connectivity with ng...

[INFO] Development server running!

       Local: http://localhost:8100
       External: http://192.168.96.2:8100, http://192.168.0.5:8100

       Use Ctrl+C to quit this process

[INFO] Browser window opened to http://localhost:8100!

   (snip)

ブラウザ でhttp://{ionic serveしたPCのIP}:8100にアクセス出来ることを確認しておきます。

PDFダウンロード、IndexedDB保存を実装

src/app/app.module.tsにHttpClientModuleを追加してHTTP通信出来るようにします。

import { HttpClientModule } from "@angular/common/http";   ← 追加

@NgModule({
    imports: [
        BrowserModule,
        IonicModule.forRoot(),
        AppRoutingModule,
        HttpClientModule    ← 追加
    ],

src/app/home/home.page.htmlを編集。

<ion-header>
    <ion-toolbar color="dark">
        <ion-title>
            PDFブラウザ表示
        </ion-title>
    </ion-toolbar>
</ion-header>

<ion-content>
    <ion-item>
        <ion-input
            placeholder="PDFファイル名"
            [(ngModel)]="pdfFileName"
        ></ion-input>
        <ion-button (click)="getPdf()">PDF取得</ion-button>
    </ion-item>

    <ion-list *ngFor="let pdf of pdfList">
        <ion-item>
            <ion-label>{{ pdf.fileName }}</ion-label>
            <ion-button (click)="showPdf()">表示</ion-button>
        </ion-item>
    </ion-list>
</ion-content>

src/app/home/home.page.tsを編集。IndexedDBへのPDF保存は以下のオブジェクト形式で行います。

{
id: 自動インクリメント,
fileName: PDFファイル名,
content: PDFのblobデータ
}

import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";

@Component({
    selector: "app-home",
    templateUrl: "home.page.html",
    styleUrls: ["home.page.scss"]
})
export class HomePage {
    // 画面のファイル名入力値
    private pdfFileName;

    // 画面のPDFリスト
    private pdfList = [];

    private dbVer: number = 1.0;
    private dbName: string = "testdb";
    private storeName: string = "pdf";

    constructor(private httpClient: HttpClient) {
        this.initIndexedDB();
        this.initScreen();
    }

    // IndexedDB初期作成
    private initIndexedDB() {
        let openRequest = indexedDB.open(this.dbName, this.dbVer);
        openRequest.onupgradeneeded = event => {
            let db = openRequest.result;

            let store = db.createObjectStore(this.storeName, {
                keyPath: "id",
                autoIncrement: true
            });
        };
    }

    // ホーム画面初期化、HTTP取得したPDFをリスト表示
    private initScreen() {
        this.pdfList = [];

        let openRequest = indexedDB.open(this.dbName, this.dbVer);
        openRequest.onsuccess = event => {
            let db = openRequest.result;
            let transaction = db.transaction([this.storeName], "readonly");
            let store = transaction.objectStore(this.storeName);

            let request = store.openCursor();
            request.onsuccess = event => {
                let cursor = request.result;
                if (cursor) {
                    this.pdfList.push(cursor.value);
                    console.log(cursor.value);
                    cursor.continue();
                }
            };
        };
    }

    // assetsに置いたPDFをHTTP GET
    public getPdf() {
        // 画面で指定された名前のPDFをblob形式でHTTP GET
        let pdfRequest = this.httpClient.get("assets/" + this.pdfFileName, {
            responseType: "blob"
        });

        pdfRequest.subscribe(data => {
            let openRequest = indexedDB.open(this.dbName, this.dbVer);
            openRequest.onsuccess = event => {
                let db = openRequest.result;
                let transaction = db.transaction([this.storeName], "readwrite");
                let store = transaction.objectStore(this.storeName);

                // PDFをIndexedDBに保存。サーバロジックが無いのでContent-Dispositionを設定出来ずファイル名が特定できない
                // ファイル名は簡単に画面入力に使われたものを使用
                let request = store.put({
                    fileName: this.pdfFileName,
                    content: data
                });
                //
                request.onsuccess = () => {
                    this.initScreen();
                };
            };
        });
    }
}

実装終わりです。

実行してみる

適当なPDFをどこかから拾ってきて、 sample1.pdfとでも名前を付け、src/assetsに配備します。

ブラウザからsample1.pdfと入力してPDF取得ボタンを押すとHTTPダウンロードしたPDFファイルがリストに追加され、IndexedDBにも保存されます。

表示ボタンは作りましたが処理実装していないので現段階では表示出来ません。保存したPDFを外部ビューワアプリに頼らずに表示する為、後編に続きます。

-html5
-, , ,

執筆者:

関連記事

B2BスマホアプリをGooglePlay、AppStoreに公開することがお勧め出来ない7つの理由とその対策

商材の性質やシーンに応じてスマホアプリをGooglePlayやAppleStoreのようなストアに公開することがマイナスに働くこともあります。 商材として価値の有る電子データをお持ちの商社さんとアプリ …

IndexedDBにストアしたオブジェクトのキー値を部分的に更新する

目次1 はじめに2 課題3 より良い方法4 まとめ5 補足 はじめに IndexedDBは「key : value」でレコードを保存するキーバリューストアです。 バリューには「単値」または「Javas …

UserAgent判定JSライブラリ「UAParser.js」と「Platform.js」の比較

(私はUAParser.jsを使っています) ネットを探すとUA文字列を解析してブラウザ判定をするコードが一杯出てきます。 でもUA解析プログラムを自前で作ってシステムに組み込むとなると、新しいブラウ …

History APIを使ってIonic(3以前のSPA)でブラウザの戻るボタンやAndroidバックキーを押すと前サイトに戻ってしまう件に対応する

Ionic2や3ではまだAngular Routerを採用していなかったので、ページ遷移をしてもブラウザ履歴が積まれず、Androidのバックキーやブラウザの戻るボタンを押すとサイトに入ってくる前のペ …

Angular、React、VueのDL数をnpmtrends.comで調べる

目次1 対象読者2 3つの内どれを選ぶべきか3 npmtrends.comについて4 比較結果5 それぞれの長所6 で、どれを採用するべきか 対象読者 これからフロントエンドの勉強をしようと考えている …


shingo nakanishi。東京で消耗中の職歴20年越え中年ITエンジニアです。「生涯現役プログラマを楽しむ」ことができる働き方探しをライフワークにしています。

19歳(1996年)から書き始めた個人日記が5,000日を超え、残りの人生は発信をして行きたいと思い、令和元日からこのサイトを開始しました。勉強と試行錯誤をしながら、自分が経験したIT関連情報を投稿しています。