もっと使おうPhoneGap/Cordova 2.0.0

第7回File APIを使ったiOS/Androidアプリケーション作成その4]

今回はFileReaderオブジェクトを使って、これまでに作成した画像ファイルとテキストファイルを読み込む手順を紹介していきます。

FileReaderオブジェクトを使ったファイルロード

PERSISTENTファイルシステムのファイル一覧を表示し、ファイル名をクリックして、ファイル内のデータを表示するアプリケーションを作成してみましょう。

新たに登場するオブジェクトとメソッドは次のとおりです。

  • FileReaderオブジェクト
  • readAsDataURLメソッド
  • readAsTextメソッド

これまでと同様、実機を使用せずにMac OS上のiOSシミュレータ、Windows上のAndroidエミュレータとPCに接続されているカメラデバイスを用いた手順を紹介します。

PERSISTENTファイルシステムのファイル一覧を表示。ファイル名をクリックして、ファイル内のデータを表示(iOS/Android)

PERSISTENTファイルシステムのファイル一覧を表示し、ファイル名をクリックすることでファイルの中身が表示されるサンプルを作成します。これまでのサンプルでは、PERSISTENTファイルシステムに画像ファイルまたはテキストファイルが書き込まれます。画像の場合はその画像を表示、テキストファイルの場合は、テキストファイルの中身を表示します。

テスト用のソースコードは次のとおりです。

リスト1 ソースコード: File API Test (5)
  1  <!DOCTYPE html>
  2  <html>
  3    <head>
  4      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  5      <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width;" />
  6      <title>File API Test (5)</title>
  7    </head>
  8    <body>
  9      <div class="app">
 10        <h1>File API Test</h1>
 11        <ul id="fileList"></ul>
 12        <div id="viewer" style="display: none">
 13          <table>
 14            <tr>
 15              <th>ファイル名</th>
 16              <td id="fileName"></td>
 17            </tr>
 18            <tr>
 19              <th>ファイルサイズ</th>
 20              <td id="fileSize"></td>
 21            </tr>
 22          </table>
 23          <div id="fileContents"></div>
 24          <input id="back" type="button" value="Back">
 25        </div>
 26      </div>
 27      <script type="text/javascript" src="cordova-2.0.0.js"></script>
 28      <script type="text/javascript">
 29      var directoryEntry;
 30  
 31      document.addEventListener('deviceready', init, false);
 32      function init() {
 33        // PERSISTENTファイルシステムを取得
 34        window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, getFilesFromDirectory, fail);
 35        document.getElementById('back').addEventListener('click', back, false);
 36      }
 37  
 38      function getFilesFromDirectory(fileSystem) {
 39        // FileSystemオブジェクトのrootプロパティには、DirectoryEntryオブジェクトが格納されている
 40        directoryEntry = fileSystem.root;
 41  
 42        // DirecotryEntryオブジェクトのcreateReaderメソッドを使い、
 43        // ディレクトリ内のファイルを読み込むためのDirectoryReaderオブジェクトを生成
 44        var directoryReader = directoryEntry.createReader();
 45  
 46        // DirectoryReaderオブジェクトのreadEntriesメソッドを使い、
 47        // ディレクトリ内のエントリを読み込み、コールバック関数に配列として渡す
 48        directoryReader.readEntries(putFileName, fail);
 49      }
 50  
 51      function putFileName(entries) {
 52        // ディレクトリ内のエントリがFileEntryオブジェクトまたは
 53        // DirectoryEntryオブジェクトとして配列で渡される
 54        for ( index = 0; index < entries.length; index++ ) {
 55          // ファイルのみを表示する (ディレクトリは表示しない)
 56          if ( entries[index].isFile ) {
 57            // ファイル名を格納する DOM を生成
 58            var listItem = document.createElement('li');
 59            listItem.textContent = entries[index].name;
 60            listItem.title = entries[index].fullPath;
 61            listItem.addEventListener('click', viewFileEntry, false);
 62  
 63            // 作成した DOM を <ul id="fileList"></ul> に挿入
 64            document.getElementById('fileList').appendChild(listItem);
 65          }
 66        }
 67      }
 68  
 69      function viewFileEntry(event) {
 70        // FileEntryオブジェクトを取得
 71        directoryEntry.getFile(event.target.textContent, null, getFile, fail);
 72      }
 73  
 74      function getFile(fileEntry) {
 75        // Fileオブジェクトを取得
 76        fileEntry.file(readFile, fail);
 77      }
 78  
 79      function readFile(file) {
 80        var reader = new FileReader();
 81        document.getElementById('fileName').textContent = file.name;
 82        document.getElementById('fileSize').textContent = file.size;
 83   
 84        // ファイルタイプの判定
 85        // Androidの場合は、Fileオブジェクトのtypeプロパティから判定
 86        // iOSの場合は、Fileオブジェクトのnameプロパティから、拡張子で判定
 87        if
 88        (
 89          ( 'Android' === device.platform && 'jpg' === file.type ) ||
 90          ( file.name.match(/\.jpg$/i) )  
 91        ) {
 92          // 画像ファイルを readAsDataURL で読み込み
 93          reader.readAsDataURL(file);
 94          reader.onloadend = function(event) {
 95            document.getElementById('fileList').style.display = 'none';
 96            document.getElementById('viewer').style.display = 'block';
 97            var img = new Image();
 98            img.src = event.target.result;
 99            img.onload = function() {
100              document.getElementById('fileContents').appendChild(img);
101            }
102          }
103        } 
104        else if
105        (
106          ( 'Android' === device.platform && 'txt' === file.type ) ||
107          ( file.name.match(/\.txt$/i) )  
108        ) {
109          // テキストファイルを readAsText で読み込み
110          reader.readAsText(file);
111          reader.onloadend = function(event) {
112            document.getElementById('fileContents').textContent = event.target.result;
113            document.getElementById('fileList').style.display = 'none';
114            document.getElementById('viewer').style.display = 'block';
115            document.getElementById('fileContents').style.whiteSpace = 'pre';
116          }
117        }
118      }
119  
120      function back() {
121        document.getElementById('fileName').textContent = '';
122        document.getElementById('fileSize').textContent = '';
123        document.getElementById('fileContents').textContent = '';
124        document.getElementById('fileList').style.display = 'block';
125        document.getElementById('viewer').style.display = 'none';
126        document.getElementById('fileContents').style.whiteSpace = 'normal';
127      }
128  
129      function fail(error) {
130        // エラーについては http://docs.phonegap.com/en/2.0.0/cordova_file_file.md.html#FileError を参照
131        alert('エラーが発生しました。エラーコード: ' + error.code);
132      }
133      </script>
134    </body>
135  </html>

まずはiOSで実行してみましょう。アプリケーションを起動すると、PERSISTENTファイルシステムに格納されているファイルの一覧が<ul><li>要素で表示されます。DirectoryEntry/FileEntryに用意されているisFileプロパティを参照し、ファイルのみを対象としています。

PERSISTENTファイルシステムに格納されているファイルの一覧が列挙される
PERSISTENTファイルシステムに格納されているファイルの一覧が列挙される

ファイル名をクリックすると、ファイルの詳細が表示されます。画像ファイルの場合は画像が、テキストファイルの場合はテキストデータを画面に表示します。

画像ファイルをクリックすると、画像が表示される
画像ファイルをクリックすると、画像が表示される
テキストファイルをクリックすると、中身が表示される
テキストファイルをクリックすると、中身が表示される

iOSシミュレータでファイルを取り扱うアプリケーションを作成する場合、XcodeのデバッグログにBase64エンコードされたファイルデータを書き出す影響で、動作が遅くなります。スキーマの編集画面を開き、Build ConfigurationをDebugからReleaseにすることで、動作速度を向上させることができます。

具体的な手順は次のとおりです。

  1. Xcodeの対象プロジェクトを選択している状態で、上部メニューの[Product]より[Edit Scheme]をクリック
  2. Build Condigurationのプルダウンを選択し、⁠Debug]から[Release]に変更
  3. 右下の[OK]ボタンをクリック
Xcodeの対象プロジェクトを選択している状態で、上部メニューの[Product]より[Edit Scheme]をクリック
Xcodeの対象プロジェクトを選択している状態で、上部メニューの[Product]より[Edit Scheme]をクリック
InfoタブのBuild Configurationを変更
InfoタブのBuild Configurationを変更
[Debug]から[Release]
[Debug]から[Release]に

Build Configurationを変更することで、トラブルの原因切り分けに必要なログも出力されなくなる場合もあります。トラブルの原因を切り分ける作業を行う際は、再度Debugに戻しておくと良いでしょう。

続いて、Androidで実行してみましょう。

PERSISTENTファイルシステムに格納されているファイルの一覧が列挙される
PERSISTENTファイルシステムに格納されているファイルの一覧が列挙される
画像ファイルをクリックすると、画像が表示される
画像ファイルをクリックすると、画像が表示される
テキストファイルをクリックすると、中身が表示される
テキストファイルをクリックすると、中身が表示される

このサンプルコードでは、おもに2画面─ファイル名を列挙する<ul><li>要素と、ファイルの詳細を表示する<div id="viewer">要素から成り立ちます。<div id="viewer">要素はアプリケーション起動時の際には非表示とし、ファイル名をクリックすることではじめて見えるようになります。行っている処理の流れは次のとおりです。

  1. requestFileSystemでPERSISTENTファイルシステムを取得
  2. FileSystemオブジェクトのrootプロパティから、PERSISTENTファイルシステムのルートディレクトリの情報(DirectoryEntryオブジェクト)を取得
  3. DirectoryEntryオブジェクトのcreateReaderメソッドを用いて、DirectoryReaderオブジェクトを作成
  4. DirectoryReaderオブジェクトのreadEntriesメソッドを用いて、ディレクトリ内のファイル/ディレクトリエントリを読み込む
  5. readEntriesメソッドから渡されたFileEntry/DirectoryEntryの配列を受け取り、ファイルのみを<li>要素で列挙。ファイル名をクリックすることでFileEntryオブジェクトを取得するように
  6. FileEntryオブジェクトからFileオブジェクトを取得
  7. FileReaderオブジェクトを作成。Fileオブジェクトと組み合わせて、ファイルデータを読み込み、ファイル情報と一緒に画面に表示する

PERSISTENTファイルシステム内のファイルを列挙するところまでは、前回のサンプルTEMPORARYファイルシステムのファイル一覧を取得とほぼ一緒です。DOMを挿入する前に、addEventListenerでファイル名がクリックされた際にviewFileEntry関数が呼び出されるようにしておきます。

55   // ファイルのみを表示する (ディレクトリは表示しない)
56   if ( entries[index].isFile ) {
57     // ファイル名を格納する DOM を生成
58     var listItem = document.createElement('li');
59     listItem.textContent = entries[index].name;
60     listItem.title = entries[index].fullPath;
61     listItem.addEventListener('click', viewFileEntry, false);
62  
63     // 作成した DOM を <ul id="fileList"></ul> に挿入
64     document.getElementById('fileList').appendChild(listItem);
65   }

viewFileEntry関数は、FileEntryオブジェクトを取得するための関数です。textContentプロパティからファイル名を取得し、PERSISTENTファイルシステムのDirectoryEntryオブジェクトからgetFileメソッドを使い、FileEntryオブジェクトを取得します。

69   function viewFileEntry(event) {
70     // FileEntryオブジェクトを取得
71     directoryEntry.getFile(event.target.textContent, null, getFile, fail);
72   }

71行目のgetFileメソッドの第3引数に指定しているgetFile関数では、FileEntryからFileオブジェクトを取得します。

74   function getFile(fileEntry) {
75     // Fileオブジェクトを取得
76     fileEntry.file(readFile, fail);
77   }
iOSシミュレータ上でのDirecotryEntry/FileEntry/Fileオブジェクト。実機で行った場合、fullPathの「/Users/(ユーザ名)/Library/Application Support/iPhone Simulator/5.1/ Applications/」の部分は「/var/mobile/Applications/」となる
iOSシミュレータ上でのDirecotryEntry/FileEntry/Fileオブジェクト。実機で行った場合、fullPathの「/Users/(ユーザ名)/Library/Application Support/iPhone Simulator/5.1/Applications/」の部分は「/var/mobile/Applications/」となる
Androidエミュレータ上でのDirecotryEntry/FileEntry/Fileオブジェクト
Androidエミュレータ上でのDirecotryEntry/FileEntry/Fileオブジェクト

76行目のfileメソッドの第1引数に指定しているreadFile関数は、Fileオブジェクトを使用してファイルを読み込みます。関数の先頭でFileReaderオブジェクトを作成します。その後、Fileオブジェクトのname, sizeプロパティからそれぞれファイル名, ファイルサイズを取得し、それぞれのDOM要素に挿入します。

79   function readFile(file) {
80     var reader = new FileReader();
81     document.getElementById('fileName').textContent = file.name;
82     document.getElementById('fileSize').textContent = file.size;

FileReaderオブジェクトのプロパティは次のとおりです。

readyState:
EMPTY(0: 何も読み込んでいない), LOADING(1: 読み込み中), DONE(2: 読み込み完了)のうち、いずれかの状態を示します
result:
readAsDataURLメソッド, readAsTextメソッドで読み込んだファイルのデータを格納します
error:
エラー発生時にFileErrorオブジェクトを格納します
onloadstart:
イベントハンドラ。ファイルのロード開始時に呼び出したい関数を指定します
onprogress:
イベントハンドラ。ファイルのロード中に呼び出したい関数を指定します。現在サポートされていない機能です
onload:
イベントハンドラ。ファイルのロード完了時に呼び出したい関数を指定します
onabort:
イベントハンドラ。ファイルのロード中止時に呼び出したい関数を指定します
onerror:
イベントハンドラ。ファイルロード失敗時に呼び出したい関数を指定します
onloadend:
イベントハンドラ。ファイルロードの成功/失敗問わずにリクエストが完了した際に呼び出したい関数を指定します

FileReaderオブジェクトに用意されているメソッドは次のとおりです(使い方のfileReaderは、FileReaderオブジェクトを指します⁠⁠。

メソッド名 内容
使い方
abort ファイルの読み込みを中止します。
fileReader.abort()
readAsDataURL ファイルを読み込み、データURL形式でresultプロパティに格納します。
  • 引数に読み込みたいFileオブジェクトを指定します。
fileReader.readAsDataURL(file)
readAsText ファイルを読み込み、resultプロパティに格納します。
  • 第1引数には読み込みたいFileオブジェクトを指定します。
  • 第2引数にはエンコーディングを指定します。デフォルト値はUTF-8、省略可能です。iOSではこの引数はサポートされておらず、常にUTF-8エンコーディングとなります。
fileWriter.readAsText(file, encoding)

コードの解説に戻ります。ここでは続けてファイルタイプの判定を行っています。Androidの場合はFileオブジェクトのtypeプロパティから、ファイルのMIMEタイプを判定します。iOSの場合はtypeプロパティがnullとなってしまったので、ファイル名の拡張子からファイルが画像であるか否かを判定します。

84  // ファイルタイプの判定
85  // Androidの場合は、Fileオブジェクトのtypeプロパティから判定
86  // iOSの場合は、Fileオブジェクトのnameプロパティから、拡張子で判定
87  if
88  (
89    ( 'Android' === device.platform && 'jpg' === file.type ) ||
90    ( file.name.match(/\.jpg$/i) )  
91  ) {
104   else if
105   (
106     ( 'Android' === device.platform && 'txt' === file.type ) ||
107     ( file.name.match(/\.txt$/i) )  
108   ) {

ファイルが画像(jpg)の場合は、FileReaderオブジェクトのreadAsDataURLメソッドを用いてファイルを読み込みます。引数に指定するのは、読み込みたいFileオブジェクトです。readAsDataURLメソッドでは、引数に指定したFileオブジェクトをデータURL形式で読み込みます。データURL形式の文字列をそのまま<img>要素のsrc属性に格納することで、画像を描画することができます。

92    // 画像ファイルを readAsDataURL で読み込み
93    reader.readAsDataURL(file);

FileReaderオブジェクトに用意されているonloadendイベントをもちいて、ファイルの読み込みが完了した時点で無名関数を呼び出します。ファイル名の列挙を非表示とし、ファイル情報の詳細を表示するための<div id="viewer">要素を可視化します。Imageオブジェクトを作成し、srcプロパティにreadAsDataURLメソッドで取得したデータURL形式の文字列を代入し、appendChildメソッドでDOMを挿入して画像を表示します。

 94    reader.onloadend = function(event) {
 95      document.getElementById('fileList').style.display = 'none';
 96      document.getElementById('viewer').style.display = 'block';
 97      var img = new Image();
 98      img.src = event.target.result;
 99      img.onload = function() {
100        document.getElementById('fileContents').appendChild(img);
101      }
102    }

ファイルがテキスト(txt)の場合は、FileReaderオブジェクトのreadAsTextメソッドを用いてファイルを読み込みます。readAsDataURLメソッド同様、引数に指定するのは、読み込みたいFileオブジェクトです。readAsTextの場合は、引数に指定したFileオブジェクトをテキスト形式で読み込みます。

109   // テキストファイルを readAsText で読み込み
110   reader.readAsText(file);

FileReaderオブジェクトに用意されているonloadendイベントをもちいて、ファイルの読み込みが完了した時点で無名関数を呼び出します。ファイル名の列挙を非表示とし、ファイル情報の詳細を表示するための<div id="viewer">要素を可視化。<div id="fileContents">要素に読み込んだテキストデータを挿入し、white-spaceスタイルにpreを指定してテキストデータを表示させます。

111   reader.onloadend = function(event) {
112     document.getElementById('fileContents').textContent = event.target.result;
113     document.getElementById('fileList').style.display = 'none';
114     document.getElementById('viewer').style.display = 'block';
115     document.getElementById('fileContents').style.whiteSpace = 'pre';
116   }

back関数はファイル情報詳細画面から一覧画面に戻るための関数です。ファイル情報が格納されている要素を初期化し、<ul id="fileList">を可視化。<div id="viewer">を非表示にします。

120  function back() {
121    document.getElementById('fileName').textContent = '';
122    document.getElementById('fileSize').textContent = '';
123    document.getElementById('fileContents').textContent = '';
124    document.getElementById('fileList').style.display = 'block';
125    document.getElementById('viewer').style.display = 'none';
126    document.getElementById('fileContents').style.whiteSpace = 'normal';
127  }

昨今ではJavaScriptでバイナリを操作するオープンソースのライブラリもいくつか登場してきています。速度の面を突き詰めると各デバイス向けのネイティブアプリケーションを実装するのがベストですが、Webの開発リソースを活かしながらデバイス上のファイル操作も行えるPhoneGapは、条件が一致すれば強力な開発プラットフォームとなるでしょう。


次回はファイル転送に関係するオブジェクトの紹介と、FileTranferオブジェクトを利用したファイルのアップロード、ダウンロードを行うサンプルアプリケーションを取りあげます。

おすすめ記事

記事・ニュース一覧