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

執筆者:

関連記事

キャッシュされているはずのServiceWorker資源にオフラインアクセス出来ない(Workbox + ionicons)

そのHTTPリクエストしたファイル資源、ひょっとしてURLパラメータついてたりしませんか? 目次1 事象2 原因3 対処 事象 ionic3(SPA)でWorkboxを使ったServiceWorker …

Android+ChromeでlocalhostアクセスしてPCサーバへPort Forward(Fwdアプリ使用)

昨今はプライバシーの侵害防止、セキュリティ観点から、HTTPS環境下でないと使えないHTML5 APIが増えました。 反してAndroid7でオレオレ証明書に関する仕様が変わり、Android6だった …

ブラウザから起動したカメラの撮影画像をjavascriptで圧縮【Compressor.js】

「モバイル用Webアプリで撮影したカメラ画像のファイルサイズが大きすぎる・・・」 そんな悩みは無いですか? 昨今カメラ会社の経営が傾くほどスマホのカメラ性能が向上、それに応じて年々ファイルサイズも増大 …

Chrome75に実装された「Web Share API Level 2」を使ってみた

(2019/06現在、個人的な感想としては実戦投入はまだ早い印象でした) Webアプリにシェア機能を付けたい時があります。OSネイティブAPIを呼べないWebアプリではsharer.jsやremote …

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

2019年時点で開発が継続しているUA判定JSライブラリから2つ選択して動作を確認しました。 目次1 Webリソースから比較1.1 github比較1.2 NPMリポジトリ比較2 実際に使用して比較2 …

 

shingo.nakanishi
 

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