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
-, , ,

執筆者:

関連記事

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

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

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

目次1 はじめに2 ビューワ機能を提供してくれるnpmモジュールを選別する3 実装開始3.1 ng2-pdfjs-viewerを使えるようにする3.2 PDFビューワコンポーネントを作る3.3 Hom …

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

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

hidden.inとSoftEtherで無料のビデオ会議環境を構築する

自粛が続いて会えない人も多くなり、人との交流が恋しくなりますね。 仕事ではミーティングをオンライン化するニーズが増え、以下メジャーなパブリックサービスを使っている人も増えていると思います。 Skype …

Android、iPhoneが向いている方向をWeb地図上で表現してみる(leaflet.js使用)

前回Javascriptでコンパス機能を実装して、Android、iPhoneの背面が向いている方角が数値(0~360:0が北)で分かるようになりました。 One IT Thing  9 P …

 

shingo.nakanishi
 

東京在勤、1977年生まれ、IT職歴2n年、生涯技術者として楽しく生きることを目指しています。デスマに負けず健康第一。