ファイルやフォルダ選択画面を作る

スプレッドシートなどに於いて、ファイルの選択が必要なケースは多々あります。それはGoogle Drive上であっても同じです。VBAであればコモンダイアログなどでファイルの選択画面はもはや当たり前の機能なのですが、Google Apps Scriptの場合には少々面倒くさいコードを記述しなければいけません。また、Google Drive自体が非常に特殊な仕様であるため、このファイル選択画面でも不便な点も多々ありますが、これしか方法がないので仕方がありません。

今回は、スプレッドシートに画像を於いて、クリックするとファイル選択の画面が出て、ファイルを選択すると、A1セルにそのファイルの情報を書き込むというスクリプトを記述しました。

今回のスクリプトのサンプルは、下記のスプレッドシートに収めております。が、APIキーは抜いてありますので、コピーして、それぞれの方でAPIキーを取得して使って見てください。

Google Driveのファイルを選択する画面を作りたい - その1

ソースコード

function init() {

//アクティブスプレッドシートの取得

   var sheet = SpreadsheetApp.getActiveSpreadsheet();
   
   //UiAppでダイアログ作成の準備とサーバーハンドラを準備
   var app = UiApp.createApplication().setTitle("ファイルを選択してください");
   var doclisthandler = app.createServerHandler('selectionhandler');
 
   //Pickerダイアログを表示する
   app.createDocsListDialog()
      .setDialogTitle('MyPicker')
      .showDocsPicker()
      .setMultiSelectEnabled(true)
      .setInitialView(UiApp.FileType.FOLDERS)
      .addSelectionHandler(doclisthandler);
   
   //appの画面サイズを指定
   app.setWidth(750).setHeight(500);
   //スプレッドシートにダイアログを表示
   sheet.show(app);
 }
 function fileselect() {
   return init();
 }
 
 function selectionhandler(e){
  var sheet = SpreadsheetApp.getActiveSpreadsheet();
  var app = UiApp.getActiveApplication();
  //Logger.log(e.parameter.items[0].id);
  //Logger.log(e.parameter.items[0].name);
  //Logger.log(e.parameter.items[0].url);
  //A1セルに取得したファイルのIDを書き込む
  sheet.getRange('A1').setValue(e.parameter.items[0].id)
  app.close();
  return app;
}

ファイル選択の様子

図:ファイル選択初期画面(フォルダビューで設定した結果)

図:ファイルの複数選択をしている様子(マルチ選択可にしている為)

ポイント

今回の機能は、UiAppの機能の一つである「DocsListDialog」を用いて実現しています。Google Driveの画面のようなものが利用できるわけです。資料が少ないので実装も甘い部分があるのですが、今回作成するに当って注目すべき点と、利用するに当っての注意点を記述します。

注目すべき点

  1. showDocsPickerにてPicker APIの機能を利用することができます。
  2. setMultiSelectEnabled(true)にて、ファイルの複数選択が可能になります。その場合、複数のファイルの情報が入ることになるので、すべてのデータを扱いたい場合には、ループでe.parameter.items[0]の配列から値を取り出していくルーチンが別途必要になります。今回のソースは実装していませんので、1個目のファイルのIDだけがA1に書き込まれます。
  3. setInitialViewにて初期表示方法を決めることが出来ます。何も指定しない場合、単純に全ファイルがバラバラ出てくるので、非常に面倒です。今回はUiApp.FileType.FOLDERSを指定して、フォルダでまずは表示するようにしています。ルート・ディレクトリに於けるフォルダがまず表示されるはずです。使用できるファイルタイプはここに一覧がありますので参考にしてみてください。
  4. そのまま表示だと収まらなかったので、setHeightとsetWidthで指定していますが、どうも一定のサイズ以上には大きくなってくれないみたい。

注意すべき点

  1. serverHandlerの使用は必須です。また、closeHandlerも必要に応じて設定が必要です。
  2. DocsListDialogのキャンセルボタンを押した時の挙動の設定がどうやったら良いのかわからんので、今回はまだ未記載です。
  3. ファイルを選択し、セルに値を書き込んだあとにapp.close()の記述がありますが、これがないとダイアログがいつまでも出たままになります。
  4. 特定のフォルダの中の特定のファイルといった具合に指定はできないみたいです。特定のファイルのIDや種類で表示は出来るみたいですけれど。
  5. ちなみにGoogle Picker APIではMIMEタイプの指定など細かくできるようです。GASでは使えませんが。

Google Driveのファイルを選択する画面を作りたい - その2

その1では、DocsListDialogを用いて、ファイル選択画面を用いて、Google Driveのファイル選択画面を作成しました。しかし、あまり見た目も良くなく、具合も悪い。そこで、Googleの拡張サービスを利用してGoogle Picker APIを用いてファイルの選択画面を作る方法が、Googleにて公開されています。但し、この方法は結構面倒臭いので、テンプレートを自分で用意しておくと良いと思います。また、この手法は、HTML Servicesも利用していますが、その1とは違い、UiInstanceは使用していません。

ソースコード

スクリプト側
function showPicker() {
  var html = HtmlService.createHtmlOutputFromFile('Picker.html')
      .setWidth(600).setHeight(425);
  SpreadsheetApp.getUi().showModalDialog(html, 'Select a file');
}
function getOAuthToken() {
  DriveApp.getRootFolder(); //なにげにこの部分重要!!
  return ScriptApp.getOAuthToken();
}
HTMLファイル側(Picker.html)
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script type="text/javascript">
  var DEVELOPER_KEY = 'ここにデベロッパーコンソールで得たPublic API Keyを入れる';
  var DIALOG_DIMENSIONS = {width: 600, height: 425};
  var pickerApiLoaded = false;
  //Google Picker API呼び出し
  gapi.load('picker', {'callback': function() {
    pickerApiLoaded = true;
  }});
  //OAuthにて認証作業
  function getOAuthToken() {
    google.script.run.withSuccessHandler(createPicker)
        .withFailureHandler(showError).getOAuthToken();
  }
  //Picker Dialogを表示する
  function createPicker(token) {
    if (pickerApiLoaded && token) {
      var picker = new google.picker.PickerBuilder()
          .addView(google.picker.ViewId.SPREADSHEETS)
          .enableFeature(google.picker.Feature.NAV_HIDDEN)
          .hideTitleBar()
          .setOAuthToken(token)
          .setDeveloperKey(DEVELOPER_KEY)
          .setCallback(pickerCallback)
          .setSize(DIALOG_DIMENSIONS.width - 2,
              DIALOG_DIMENSIONS.height - 2)
          .build();
      picker.setVisible(true);
    } else {
      showError('Unable to load the file picker.');
    }
  }
  //Callbackデータを受け取る
  function pickerCallback(data) {
    var action = data[google.picker.Response.ACTION];
    if (action == google.picker.Action.PICKED) {
      var doc = data[google.picker.Response.DOCUMENTS][0];
     var id = doc[google.picker.Document.ID];
     var url = doc[google.picker.Document.URL];
      var title = doc[google.picker.Document.NAME];
      document.getElementById('result').innerHTML =
          '<b>You chose:</b><br>Name: <a href="' + url + '">' + title + '</a><br>ID: ' + id;
    } else if (action == google.picker.Action.CANCEL) {
      document.getElementById('result').innerHTML = 'Picker canceled.';
    }
  }
  //エラー表示用
  function showError(message) {
    document.getElementById('result').innerHTML = 'Error: ' + message;
  }
</script>
<div>
  <button onclick='getOAuthToken()'>Select a file</button>
  <p id='result'></p>
</div>

Google Picker APIをONにしている様子

スクリプトエディタから表示させた、Googleの拡張サービス。しかし、ここにはPicker APIはないので、下のデベロッパーコンソールへのリンクを開く

デベロッパーコンソール側でONにした様子

Credentialsにて、Public APIを取得している様子

ファイルを選択している様子

スプレッドシート上でGoogle Picker APIにて表示させてみた図

選択をした後の画面。IDとファイル名が返ってきた図。

Google Picker APIを有効にする手順

  1. スクリプトエディタのメニューから、[リソース] -> [Googleの拡張サービス]を開く
  2. 下にある「Googleデベロッパーコンソール」のリンクを開く
  3. Google Picker APIをONにする
  4. 左のメニューにあるCredentialsをクリックする
  5. Public API Accessの中にあるCreate New Keyをクリックする
  6. ブラウザアプリケーションを選択します
  7. Reffererとして、*.google.com*.googleusercontent.comの2つを入力しておく。(独自ドメインの場合には、そのドメインも入れて置かないと実行出来ません)
  8. 出てきたAPIキーをコピーしておく
  9. 上記ソースコードの中でHTML側のDEVELOPER_KEYの変数にこのAPI Keyを記述して入れて上げる

※使用するためには、Googleデベロッパーコンソールが使えるようになっていることが必要です。

ダイアログ側から値を取り出す為には

選択した結果を受け取ったり、格納して引き続き別のスクリプトへ渡してあげる為には、その為の関数を用意する必要性があります。上記のコードだけでは表示されただけで受け取ることができません。そこで追加の関数と引き渡すメソッドを用意してあげれば、引き続きスクリプト側で、受け取った値を持って作業を続行することができます。

//コードを取得したら次のコードを付け足しておく。(上記ソースならCallbackを受け取るのソースの中)
google.script.run.telepon(id);

今回は上記ソースでは、var id = doc[google.picker.Document.ID]; で値を1つ受け取っているので、これをgoogle.script.run.telepon(id)という形で、予め用意しておいたtelepon関数に引数で渡してあげています。

function telepon(targetval){
  ScriptProperties.setProperty('uid',targetval);
  Browser.msgBox(targetval);
}

今回は、上記コードで、受け取った値をsetPropertyにてプロジェクトプロパティに格納した上で、メッセージボックスを表示させています。

こうすることで、HTML Servicesで表示させたダイアログに表示したPicker APIウィンドウでの値を渡すことが可能です。

ポイント

Picker APIの設定は、上記ソースコードにもあるように、Picker.Builderに対してオプションを渡して上げることで出来ます。主に使用するであろう追加オプションは

  1. .setLocale('ja') で日本語インターフェースになります。
  2. .enableFeature(google.picker.Feature.MULTISELECT_ENABLED) で、複数選択モードになります。FLASEDで単一選択モードとなります。
  3. .addView(google.picker.ViewId.SPREADSHEETS) で、スプレッドシートのみがフィルタされて表示されます。このViewIdを変更することで、ファイルのフィルタが可能になります。
  4. 但し、この方法は、APIの利用に制限があります。1アプリケーションに付き24時間で10,000アクセスまでの制限となっています。複数のアプリケーションで使っても累計ではなく、アプリケーション毎になりますので、小規模であれば問題ないでしょう。
  5. デベロッパーコンソールで得たPublic API Keyは使い回しが出来ますが、カウントされてしまうので、普通はアプリケーション毎に取得して配置します。

Google Driveのフォルダを選択する画面を作りたい

これまでは、Google Driveのファイル単位での選択画面を作る方法を紹介しましたが、そうなってくると欲しくなるのが「フォルダの選択画面」です。但し、Google Drive上ではフォルダは通常のPC上のフォルダとはことなり、タグみたいなものを具現化したに過ぎない存在なのでアレなんですが、その方法はないか?と思って探していたのですが、なかなか実現できませんでした。しかし、ようやくやり方がわかったので記録しておこうと思います。この手法は、上記のGoogle Driveのファイルを選択する画面を作りたい - その2を少し改造するだけで作成できます。HTML側のみ編集しますので、スクリプト側の記述は省略します。

リファレンスはここにあるのですが、読んでもさっぱりでした。今回のソースも、フォルダのIDだけを拾って、自前の関数に渡して上げています。こうすることで、フォルダのIDをゲットしてスクリプトプロパティに値を格納することができるので、スクリプト側でそれを元に作業をしやすくなります。

ソースコード

HTML側(Picker.html)
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script type="text/javascript">
  var DEVELOPER_KEY = 'ここにデベロッパーコンソールで得たPublic API Keyを入れる';
  var DIALOG_DIMENSIONS = {width: 750, height: 480};
  var pickerApiLoaded = false;
  //Google Picker API呼び出し
  gapi.load('picker', {'callback': function() {
    pickerApiLoaded = true;
  }});
  //OAuthにて認証作業
  function getOAuthToken() {
    google.script.run.withSuccessHandler(createPicker)
        .withFailureHandler(showError).getOAuthToken();
  }
  //Picker Dialogを表示する
  function createPicker(token) {
    if (pickerApiLoaded && token) {
      var docsView = new google.picker.DocsView()
          .setIncludeFolders(true) 
          .setMimeTypes('application/vnd.google-apps.folder')
          .setSelectFolderEnabled(true);
            
      var picker = new google.picker.PickerBuilder()
          .addView(docsView)
          .enableFeature(google.picker.Feature.NAV_HIDDEN)
          .hideTitleBar()
          .setOAuthToken(token)
          .setDeveloperKey(DEVELOPER_KEY)
          .setCallback(pickerCallback)
          .setSize(DIALOG_DIMENSIONS.width - 2,
              DIALOG_DIMENSIONS.height - 2)
          .build();
      picker.setVisible(true);
    } else {
      showError('Unable to load the file picker.');
    }
  }
  //Callbackデータを受け取る
  function pickerCallback(data) {
    var action = data[google.picker.Response.ACTION];
    if (action == google.picker.Action.PICKED) {
      var doc = data[google.picker.Response.DOCUMENTS][0];
      var id = doc[google.picker.Document.ID];
      var url = doc[google.picker.Document.URL];
      var title = doc[google.picker.Document.NAME];
      document.getElementById('result').innerHTML =
          '<b>You chose:</b><br>Name: <a href="' + url + '">' + title + '</a><br>ID: ' + id;
      google.script.run.telepon(id);
    } else if (action == google.picker.Action.CANCEL) {
      document.getElementById('result').innerHTML = 'Picker canceled.';
    }
  }
  //エラー表示用
  function showError(message) {
    document.getElementById('result').innerHTML = 'Error: ' + message;
  }
</script>
<div>
  <button onclick='getOAuthToken()'>Select a file</button>
  <p id='result'></p>
</div>

フォルダを選択している様子

フォルダ選択画面です(フォルダの文字列をクリックすると中に入るが、チェックで選択となる)

ポイント

  1. var docsView = new google.picker.DocsView()の下りで、DocsViewクラスを定義し、.setSelectFolderEnabled(true);でフォルダ選択をオプション追加している。
  2. 1.で定義したdocsViewをこれまでもあったスクリプトのaddviewにつっこむ(以前はここはスプレッドシートを指定していた)
  3. あとは、今までと同じなので、マルチセレクトなオプションしていしたりすればゴージャスになる。
  4. docsViewのオプションとして、.setParent("フォルダのID")を指定すると、起動時にそのフォルダから表示されるので便利です。当たり前ですが、事前にフォルダのアクセス権がない人は実行できません。
  5. 稀に、そこにフォルダは確かにあるはずなのに、ドキュメントがないと表示される場合があります(バグですかね)。もう一度そのフォルダを開直すと出てきます。

ローカルのPC内のファイルを選択する画面を作りたい

ソースコード

var FOLDER_NAME = 'ここにアップロード先フォルダのIDを入れる'; //格納する為のフォルダIDを入れるためのグローバル変数
function filegetter(e) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet();
  var app = UiApp.createApplication();
  app.add( app.createHorizontalPanel().add(app.createHTML('<h1>' + 'アップローダー' + '</h1>')));
  
  var form = app.createFormPanel()
      .setEncoding('multipart/form-data');
  form.add(
    app.createHorizontalPanel().add(
      app.createFileUpload().setName('UploadFile').setId('UploadFile')
    ).add(app.createSubmitButton('アップロード') ));
  app.add(form);
  sheet.show(app);
}
function doPost(e){
  var sheet = SpreadsheetApp.getActiveSpreadsheet();
  var uploadflag = 0; //アップロード成功時に使用するフラグ
  var app = UiApp.getActiveApplication();
  
  try{
    if (e.parameter.UploadFile.length > 0){

//アップロード先のフォルダの取得

      var targetFolder = DriveApp.getFolderById(FOLDER_NAME);  //面倒ならgetRootFolder()で、ルートディレクトリにしてしまっても可
      
      //アップロードしたファイルのIDを取得するために変数に格納しファイルを生成
      var pon = targetFolder.createFile(e.parameter.UploadFile);
      //アップロードフラグを1にする
      uploadflag = 1;
      message = 'アップロード完了';
    } else {
      message = 'ファイルが選択されていません';
    }
  } catch (error){
    message = 'エラー!' + error.message;
  }
  

//アップロードファイルのIDを取得し、A1セルに値を書き込む

  if(uploadflag == 1){
    sheet.getRange('A1').setValue(pon.getId());
  }
  

//ダイアログを閉じてメッセージを表示

  app.close();
  Browser.msgBox(message);  
  
  return app;
}

ファイル選択の様子

図:ファイル選択中の画面

図:ファイルアップロード選択ダイアログ

図:アップロード後の特定フォルダ内に入ったファイル

ポイント

今回の機能は、UiAppの機能の一つである「FileUpload」を用いて実現しています。VBAのコモンダイアログの画面のようなものが利用できるわけです。今回このスクリプトは「とまと あんらいぷ…」というサイトに記載されていたものを少々改造して利用しました。今回作成するに当って注目すべき点と、利用するに当っての注意点を記述します。

注意すべき点

  1. 今回のファイルアップロード機能に於いて、ファイルの選択は単一のファイルしか選択することができません。
  2. serverHandlerなどは必要ありませんが、必ずdoPostにてアップロードの際のスクリプトを記述する必要性があります。
  3. アップロードしたファイルのファイル名やIDを取得したい場合には、createFileにて生成したものを変数に格納しておいて、そこからgetName()やgetId()などして取得することができます。取得後そのまま、続けてその名前やIDを元にスクリプトを継続することが可能です。
  4. 拡張子の制限などが出来ません。よって、アップロードされたファイル名から拡張子を抽出して判定し、どうにかするルーチンを作ってエラートラップをしてやらないと、予期せぬ動作につながる可能性はあります。

参考リンク