Pickerでファイルやフォルダを選択する画面を装備する
G Suiteでは、Google Driveを中心として様々なアプリ同士が連結しています。ファイルを選択したりアップロードしたり、そのための選択画面が用意されていてお互いでそれらを利用しています。この機能をGoogle Pickerと呼びます。
Google Apps Scriptや通常のウェブサイトのJavaScriptでこのGoogle Driveでの選択ダイアログを装備する事が可能になっています。今回はGoogleスプレッドシート上で利用するのを想定して装備をしてみます。一度使えるようになると今後手放せなくなる機能です。
図:こんな感じのダイアログが使えます
※2021年3月31日を持って、Picker APIでアクセスできるのは、Google Drive内のみとなり、PhotoやMapなどはaddViewで追加ができなくなりました。
事前準備
今回使用するスプレッドシート
今回のエントリーではアップロード機能やマップなどの他の機能には触れていません。もっとも基本的なファイルの選択とフォルダの選択にフォーカスしています。別途APIキーが必要ですが、コード内に記述する必要はありません。
使い方はメニューに表示される「チョイス」の中に3種類のファイル選択・フォルダ選択画面を用意しています。
プロジェクトを移動
APIキーの準備
今回の機能は、Google Cloud ConsoleよりGoogle PickerをONにした上で、認証情報にてAPIキーを作成する必要があります。また、GAS内ではなく一般のサイト上で使う場合には、APIキーではなくOAuth2クライアント認証が必要になるので、クライアントキーとクライアントシークレットなどが必要になります。
今回は、Googleスプレッドシート上で使用するのでAPIキーのみでOKです。取得方法については、こちらのエントリーにて取得して下さい。取得したらメニューより、「チョイス」⇒「APIキー登録」から登録してください。スプレッドシートのスクリプトプロパティにdevkeyとして値が保存されます。
共通するコード
Google Pickerを利用するための共通のコードです。Google PickerはHTML Serviceで実現している点といくつかの情報をHTML側へ渡してあげる必要があります。
GAS側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//Pickerダイアログを表示するコード function fileman() { var html = HtmlService.createHtmlOutputFromFile('Picker.html') .setWidth(600).setHeight(425); SpreadsheetApp.getUi().showModalDialog(html, 'ファイルの選択'); } //HTML側でチョイスしたファイルのIDを表示する function telepon(targetval){ var ui = SpreadsheetApp.getUi(); ui.alert(targetval); } //Pickerに渡す情報を組み立てる function getPickerInfo() { //デベロッパーキーを取得する var Properties = PropertiesService.getScriptProperties(); var devkey = Properties.getProperty("devkey"); //Access Token, 親フォルダID, Developer Key, ダイアログのサイズ, originなどをセットし、return DriveApp.getRootFolder(); return { token: ScriptApp.getOAuthToken(), developerKey: devkey, dialogDimensions: {width: 600, height: 425} }; } |
- filemanは単純にHTML ダイアログボックスを表示するだけのコードです。Picker.htmlにPickerに関するコードを記述する事になります。
- teleponはPicker側で選択したファイルのIDを受け取って、ダイアログで表示するだけの関数です。
- getPickerInfoが今回の主役。ここでは、保存してあるAPIキー(devkey)、アクセストークン(token)、そしてPicker表示領域の縦横のサイズを送ります。
この時、filemanで設定してるダイアログのサイズとgetPickerInfoで規定してるPickerのサイズは同じにしましょう。getPickerInfo側の数値がfilemanで指定してる値より大きいと、はみ出してしまいます。
HTML側コード
Picker.html側のコードです。ここでは、Pickerの構築、呼び出し、選択時の挙動などを記述してゆきます。詳細はそれぞれの項目で。ここでは共通する部分だけを記述しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css"> <script type="text/javascript" src="https://apis.google.com/js/api.js"></script> <script type="text/javascript"> var pickerApiLoaded = false; var origin = google.script.host.origin; //Google Picker API呼び出し gapi.load('picker', {'callback': function() { pickerApiLoaded = true; }}); //OAuthにて認証作業 function getOAuthToken() { google.script.run.withSuccessHandler(createPicker) .withFailureHandler(showError).getPickerInfo(); } ・・・・中略・・・・ </script> <div id="main"> <button onclick='getOAuthToken()' class="action">ファイルを選択</button> <p id='result'></p> </div> |
- 外部ライブラリとしてapi.jsをロードさせています。
- ボタンのデザイン用として、addon-cssを利用しています。
- gapi.loadにてGoogle Picker APIを呼び出しています。Cloud ConsoleでAPIを有効にしておかないと機能しません。
- originは今回は、google.script.host.originで取得させています(HTML Serviceでだけ使えるコマンドです)
- ファイル選択ボタンをクリックすると、getOAuthTokenが実行され、GAS側のgetPickerInfoが呼び出されます。その後、データを取得して、createPickerが実行される事になります。
origin設定について
Google Pickerを利用する上でつまづくポイントとして、このorigin設定があります。これはセキュリティの観点から指定したドメイン以外からはロードさせないためのもので、Picker APIを利用する上では必須の項目です。
Google Spreadsheet上で使う場合には、https://docs.google.comとなります。Google Sites上で貼り付けて使う場合には、https://sites.google.comとなります。また、単独でウェブアプリケーションとして使う場合には、https://script.google.comとなります。今回、originの値はgoogle.script.host.originで取得した値を使っています。
但し、1つのスクリプトを例えばウェブアプリケーション単体とSitesで共用する場合には、動かない事もあります。その場合の回避策は以下のコードをgetPickerInfoに追記し、HTML側で取り出してあげると良いです。
GAS側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//originを取得する var origin = "https://script.google.com"; try{ SitesApp.getActivePage().getUrl(); origin = "https://sites.google.com"; }catch(e) { } //originとしてoriginの値をセットしておいてあげる DriveApp.getRootFolder(); return { token: ScriptApp.getOAuthToken(), origin: origin, developerKey: devkey, dialogDimensions: {width: 600, height: 425} }; |
SitesApp.getActiovePage().getUrl()は旧Google Sitesでないと使えないので注意。新Google Sitesに対応させる場合には、originの初期値をhttps://sites.google.comとし、try文の中では、ScriptApp.getService().getUrl()にてURLを取得させるように改変すると良いです。
※ちなみにこのsetPriginの値ですが、スプレッドシートからの呼び出し時は「https://docs.google.com」、HTML Service上のウェブアプリケーションからの呼び出し時は「https://script.google.com」をセットする必要があります。自前のサーバの場合は自前のサーバのドメインを指定しないと、Picker APIを呼び出せません。
HTML側コード
1 2 3 4 5 6 |
//Picker Dialogを表示する function createPicker(data) { var origin = data.origin ・・・・中略・・・・ } |
getPickerInfo側からの値はcreatePickerの引数であるdataに入ってきます。data.originでその値を取り出してセットしてあげるわけです。この後のPickerの項目で、.setOriginに直接この値を引数に入れてあげれば良いのです。
ファイル選択画面
Google Pickerはオプション項目が非常に多彩で、組み合わせによってダイアログに様々な機能の追加や制限を付けることが可能です。詳細はリファレンスを見ながら自分にあったダイアログを構築する必要があります。今回は、2種類のダイアログを用意してみました。1つ目はスプレッドシートだけを表示し選択出来るようにしたもの。2つ目は詳細に細かく設定項目を追加したものです。
スプレッドシートのみを表示する
表示をスプレッドシートに制限する表示方法です。フォルダ等は出てこなくなります。制限を外すと、通常のシンプルなファイル選択画面になります。違いはそこだけです。
HTML側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
//Picker Dialogを表示する function createPicker(data) { if (pickerApiLoaded && data.token) { var docsView = new google.picker.DocsView() .setIncludeFolders(false) .setMimeTypes('application/vnd.google-apps.spreadsheet') .setSelectFolderEnabled(false); //Pickerに値をセットする var picker = new google.picker.PickerBuilder() .addView(docsView) .enableFeature(google.picker.Feature.NAV_HIDDEN) .hideTitleBar() .setLocale('ja') .setOAuthToken(data.token) .setOrigin(origin) .setDeveloperKey(data.developerKey) .setCallback(pickerCallback) .setSize(data.dialogDimensions.width - 2, data.dialogDimensions.height - 2) .build(); //Pickerを表示する picker.setVisible(true); } else { document.getElementById("main").innerHTML = '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.'; } } |
解説
- createPickerのdocsViewにて表示するファイルのMIME TYPEを指定し、制限を加えています。GoogleスプレッドシートのMIME TYPEを今回は指定していますが、別のものも指定できます。こちらを参照してください。
- docsViewにてフォルダ表示オフやフォルダ選択オフを指定しています。
- 変数pickerでは、値を色々セットしています。setLocaleでjaを指定しないとUIが日本語表示されません。
- pickerでのset項目は.addView、setOAuthToken、setOrigin、setDeveloperKey、setCallback、setSizeは必須項目です。
- .enableFeature(google.picker.Feature.NAV_HIDDEN)を指定しているので、上部のナビゲーションバーは非表示になります。
- setCallbackで指定した引数は次のpickerCallbackの関数を指定します。ファイルを選択時に発動します。
- pickerCallBackでは、取得したファイルの情報を取得し、GAS側のtelepon関数に渡して上げています。
- pickerCallbackに於いて、actionで受け取った情報からはurl、id、ファイル名などが取得可能です。取得できるデータはこちらを参照しましょう。
図:Googleスプレッドシートのみを表示させている
ファイルに制限は掛けず所有者のファイルのみ表示する場合
スプレッドシートのみに制限するのではなく、アクセスしている所有者のファイルだけを表示といったことも可能。その場合はdocsviewの組み立て方は以下の通りです。
1 2 3 4 |
var docsView = new google.picker.DocsView() .setIncludeFolders(true) //フォルダ表示ON .setOwnedByMe(true) //所有者のファイルだけ .setSelectFolderEnabled(false); //フォルダ選択不可 |
setOwnedByMeを入れてあります。ファイルは全種類表示されます。
共有ドライブサポート
ファイルの表示に於いて、「共有ドライブ内のファイル」も表示したい場合には、new google.picker.PickerBuilder()以下にオプションとして、「.enableFeature(google.picker.Feature.SUPPORT_TEAM_DRIVES)」を追加する事で、リストに共有ドライブ内のファイルも表示されるようになります。
詳細な設定を追加したファイル選択画面
通常のドライブ上でのファイル選択ダイアログのような画面を作るためには、更に詳細な設定を追加してあげる必要があります。今回はフォルダも表示させた上で、Googleスプレッドシートのみを選択するような画面にしてあげています。複数のdocsViewを追加してる点に注意。
HTML側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
//Picker Dialogを表示する function createPicker(data) { console.log(data) if (pickerApiLoaded && data.token) { var docsView = new google.picker.DocsView() .setIncludeFolders(true) .setMimeTypes('application/vnd.google-apps.spreadsheet') .setSelectFolderEnabled(false); var docsshare = new google.picker.DocsView() .setOwnedByMe(false) .setIncludeFolders(true) .setSelectFolderEnabled(true); let recentView = new google.picker.DocsView(); recentView.xd = '最近使用したファイル'; recentView.mc.sortKey = 15; //Pickerに値をセットする var picker = new google.picker.PickerBuilder() .addView(docsView) //通常のファイル選択ビュー .addView(docsshare) //共有ファイルビュー .addView(recentView) //最近使用したファイルビュー .enableFeature(google.picker.Feature.MULTISELECT_ENABLED) //複数選択可 //.enableFeature(google.picker.Feature.NAV_HIDDEN) .hideTitleBar() .setLocale('ja') .setOAuthToken(data.token) .setOrigin(origin) .setDeveloperKey(data.developerKey) .setCallback(pickerCallback) .setSize(data.dialogDimensions.width - 2, data.dialogDimensions.height - 2) .build(); //Pickerを表示する picker.setVisible(true); } else { document.getElementById("main").innerHTML = 'Pickerをロード出来ませんでした。'; } } //Callbackデータを受け取る function pickerCallback(data) { var selectman = "" var action = data[google.picker.Response.ACTION]; if (action == google.picker.Action.PICKED) { //複数選択可の場合のファイル情報ピックアップ for (var i in data.docs) { //n個目のドキュメント情報を取得 var doc = data[google.picker.Response.DOCUMENTS][i]; //ドキュメントの詳細なデータを取得 var url = doc[google.picker.Document.URL]; var title = doc[google.picker.Document.NAME]; var id = doc[google.picker.Document.ID]; //resultエレメントに追記 document.getElementById('result').innerHTML += '<b>選択した物:</b><br>ファイル名: <a href="' + url + '">' + title + '</a><br>ID: ' + id; //取得IDだけを追記する selectman += id + "\\n" } //GAS側へ送る google.script.run.telepon(selectman); } else if (action == google.picker.Action.CANCEL) { document.getElementById('result').innerHTML = 'Picker canceled.'; } } |
解説
- docsshareという項目に2つ目の「共有されたフォルダ」を表示させる為のdocsViewを追加しています。
- recentViewだけは非常に特殊です。公式リファレンスにない項目で、このような指定をすると「最近使用したファイル」が表示できます。公式では「最近選択したファイル」しか項目がありません。裏技の一つです。
- pickerでは複数の.addViewでナビゲーションバーにViewを追加してあげています。ナビゲーションバーをオフにしていると意味がありませんので注意。
- 今回はさらに.enableFeature(google.picker.Feature.MULTISELECT_ENABLED)にて、ファイルの複数選択を許可させています。
- ファイルの複数選択を可能にした為、pickerCallback側も複数のファイル情報を取り出せるようにルーチンをループで取り出しています。
- ui.alertで改行表示させるために、IDに続けて"\\n"というエスケープシーケンスを入れてあります。
- CtrlキーやShiftキーを押しながらクリックすると、複数ファイルが選択出来るようになります。
図:CtrlキーやShiftキーで複数選択が出来ますよ
フォルダ選択画面
フォルダの選択を可能にするコードです。こちらはシンプルに作っています。但し、フォルダ選択画面なのにファイルが選択できては困るので、制限を加えています。
HTML側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
//Picker Dialogを表示する function createPicker(data) { if (pickerApiLoaded && data.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() .setLocale('ja') .setOAuthToken(data.token) .setOrigin(origin) .setDeveloperKey(data.developerKey) .setCallback(pickerCallback) .setSize(data.dialogDimensions.width - 2, data.dialogDimensions.height - 2) .build(); picker.setVisible(true); } else { showError('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.'; } } |
解説
- createPickerのdocsViewにて.setMimeTypes('application/vnd.google-apps.folder')とし、フォルダのみを表示させています。
- フォルダの選択を可能にするために、同じく.setSelectFolderEnabled(true)を追加しています。
- 他はファイル選択の場合と同じです。
こうする事で、フォルダのみを表示しフォルダのみが選択できるようになるので、ファイルを選択するような事にはなりません。また、こちらの設定も詳細な設定を追加すれば、共有フォルダを選択させたりスター付きだけを表示させたりなど、多彩な追加が可能です。
その他のテクニック
2022年改定された内容
しばらくPicker APIを使っていなかった間に、少しPicker APIのロードする仕様が変更されていたようです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!-- Load the Google API Loader script. --> <script async defer src="https://apis.google.com/js/api.js" onload="onApiLoad()"></script> <script type="text/javascript"> function onApiLoad() { gapi.load('picker', onPickerApiLoad); } function onPickerApiLoad() { pickerApiLoaded = true; tokengetter(); } </script> |
- 冒頭の呼び出すライブラリがapis.google.com/js/api.jsに変更されています。
- その読み込みが終わったら、onApiLoadで初期化をし、続けて、onPickerApiLoadを呼び出すようになっています。
もし、現在利用してるPicker APIがうまく動いていないケースでは上記のように冒頭の部分を書き直す事で、動作するようになります。また、その際に一部、変なスクロールバーの有無が気になる点があったので、以下のCSSも追加しています。
1 2 3 4 5 6 7 8 9 |
<style> ::-webkit-scrollbar{ display: none; } html, body{ overflow: hidden; } </style> |
サイドバーからPickerを起動して値を取得する
自分はサイドバーにアプリの設定関係をよく用意していますが、ここに例えばテンプレートのスプレッドシートのIDを入れるシーン等に於いて、Google Pickerを呼び出してIDを取得したいケースがあります。しかし、Pickerのダイアログとサイドバーは全く別のプロセスである為、直接的にやり取りをする事はできないため、PickerでファイルのIDを取得しても、それをダイレクトにサイドバー側に渡す事が不可能です(プッシュして送り込めない)
このようなケースの場合、PickerのIDを格納する場所をグローバル変数やスクリプトプロパティに格納し、呼び出したサイドバー側のメソッドは、無限ループでその値が変化するまで待機させておく必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
//フォルダ指定場所 function saveSetting(title){ //インスタンス変数を初期化 let flags = false; //監視先プロパティを初期化 let prop = PropertiesService.getScriptProperties(); prop.setProperty("targetid",0) //Pickerを表示する let html = HtmlService.createHtmlOutputFromFile('Picker.html') .setWidth(750) .setHeight(480); SpreadsheetApp.getUi().showModalDialog(html, title); //監視用変数 let temp = ""; //ループでtargetIdが入ってくるまで待機 while (flags == false) { //プロパティを見る temp = prop.getProperty("targetid"); if(temp == 0){ //まだ値が入っていない }else{ flags = true; } } //値を返す return temp; } //targetIdを保存する function saveTargetId(ret){ //res先のプロパティに保存もする let prop = PropertiesService.getScriptProperties(); prop.setProperty("targetid",ret) return 0; } |
- saveSettingでPickerを呼び出し、スクリプトプロパティの初期値を0にしています
- Picker側でsaveTargetIdにてスクリプトプロパティに対象のファイルのIDを格納します。
- saveSetting側はWhileにてフラグがtrueになるまで無限ループで監視しつづけます。
- 値が0から変更されたら、サイドバー側にターゲットのIDを返してあげる。
図:直接通信が出来ないので仕組みが必要
レスポンシブ対応
Google Pickerのダイアログは、最低サイズは 566 * 350となっており、またスマートフォンで利用するにはちょっと不便なPC向けの仕様になっています。また、ウィンドウに合わせての調整もできないのですが、CSSを適用する事でサイズを調整する事が可能です。
1 2 3 4 5 |
.picker { height: 100% !important; width: 100% !important; top: 0 !important; } |
このCSSを加えておくことでフルスクリーン表示となります。
Pickerのダイアログに要素は追加できない
Pickerで表示されるダイアログですが、あの中身は同一ドメイン上の内容ではなくiframeで外部のドメインの内容を表示してるものになります。よってqueryselectorを使って内容を取得したり、逆にPicker Dialogに対してJavaScriptを利用し要素を追加するといったことは出来ません。
何かダイアログに対してオプションを用意したくなりますが、現実的には出来ないので、ご注意ください。