GoogleスプレッドシートではCtrl+Fで表示される検索窓から単語の検索ができますが
検索にヒットすると画面がスクロールしたりして、地味に使い辛いことがあります。
今回は、GAS (Google Apps Script) を使って、スプレッドシートのデータを検索できる簡単なWeb画面を構築してみたいと思います。
やりたいこと
今回やりたいことはこんな感じです。
このような内容のスプレッドシートをデータソースとします。
アプリを起動すると、取得された上記スプレッドシートのデータが、ブラウザでこのように見えます。
検索フィールドに値を入れて検索することも可能です。
検索フィールドに「B3」を入れて「検索」ボタンを押してみると
検索ワードに該当した部分が、色付けされたうえで表示されます。
スクリプトエディタの起動
GASを記述、動作させるには、スクリプトエディタを使用します。
スクリプトは単体でも作成・起動することができますが、今回はスプレッドシートからデータを取得するということもあり、スプレッドシートに付属するスクリプトという形で作成することにします。
まずはスプレッドシートのメニューバーにある「ツール」をクリックし、「スクリプトエディタ」を起動。
(’※「ツール」に見当たらない場合は、「拡張機能」にあるはず)
するとこのように「無題のプロジェクト」として、デフォルトで生成される関数が表示されます。
関数名は自由に変更可能です。ここにメインとなるスクリプトを記述していきます。
doGetの実装
まずはページ読み込み時に行う動作を記述します。メソッド名はdoGetです。
これはGASでWebアプリケーションを実行したときに最初に実行されるメソッドとなります。ここでは次の3つの処理を行います。
- スプレッドシートに格納されている値をパラメータへ格納
- Webブラウザで表示するhtmlファイルを生成
- 1.のパラメータを2.へセット
上記の処理により、スプレッドシートに既に書き込まれている表データが、初期表示されるWeb画面へ表示されることになります。
スプレッドシートに格納されている値をパラメータへ格納
ではソースコードを説明していきます。まずはスプレッドシートに格納されている値を読み込み、”myData”として格納しておく部分です。
let myData = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
スプレッドシートに値が存在する領域は、全て読み込み対象としています。
Webブラウザで表示するhtmlファイルを生成
Webブラウザで表示するにはhtmlファイルが必要で、そちらの生成処理もdoGetで行います。
htmlのファイル名は「index.html」とすることにします。この場合は文字列”index”を、HtmlServiceのcreateTemplateFromFileというメソッドに指定します。
let template = HtmlService.createTemplateFromFile("index");
htmlのパラメータへ値をセット
スプレッドシートの値をhtmlファイルで表示する為、値の受け渡しを行います。
ここでは、値が格納されている変数を”myData”とします。
template.myData = myData;
最後に、テンプレートの評価を返して終了です。全体のdoGetコードは以下のようになります。
function doGet() { let myData = SpreadsheetApp.getActiveSheet().getDataRange().getValues(); // スプレッドシートの値を取得し"myData"として格納 let template = HtmlService.createTemplateFromFile("index"); // index.htmlファイルを"template"として生成 template.myData = myData; // "template"にある"myData"パラメータに、取得したスプレッドシートの値"myData"をセット return template.evaluate(); }
doPostの実装
doPostには、画面上で「検索」ボタンが押下されたときの振る舞いを定義します。
いろいろと細かいことをやっていますが、大まかに以下の処理を行うことにしました。
- 検索文字列によるシート内検索を実行し、ヒットしたセルの情報を取得
- 1.で取得した情報から、ヒットしたレコードの行番号(シート内の第何行目か)を配列化
- 2.の配列に格納されている行に該当する情報をhtmlへセット
上記の処理により、検索にヒットしたレコードのみがWebページに表示されることになります
検索にヒットしたセルの情報を取得
順に見ていきます。検索文字列によるシート内検索は、TextFinderクラスのfindAll()メソッドを使用します。
let ranges = sheet.createTextFinder(searchW).findAll();
この部分で、入力された検索文字列を元にシート内検索を行い、該当するセル情報を取得します。
戻り値はRange[]で、該当する全てのセルの情報が格納された配列となります。
ヒットしたレコードの行番号を配列化
先に取得したセルの情報から、レコード番号を格納します。
Range[]にはセルの情報が入っているので、1行に複数のセルがヒットした場合には、その分が重複してしまいます。
なので一旦、行数を配列化しておき、その後重複を排除するというやり方を採ります。
// 検索にヒットしたRangeとレコード行を格納 for (let i = 0; i < ranges.length; i++) { targetRows.push(ranges[i].getRow()); hitRanges.push([ranges[i].getRow(), ranges[i].getColumn()]); } // 1行に複数個ヒットした場合の重複行番号排除 targetRows = targetRows.filter(function(x, i, self) { return self.indexOf(x) === i; });
後で該当するセルに色付けしたいので、そのために個々のセルの行と列の情報も別途保持しています。
該当するレコードをhtmlにセット
上記までに取得した情報を、htmlにセットする「myData」パラメータとしてセットします。
途中で(コードに直書きでアレですが)該当するセルへの背景色付与も行っています。
for (let i = 0; i < targetRows.length; i++) { // 検索にヒットしたレコードの取得 let data = sheet.getRange(targetRows[i], 1, 1, sheet.getLastColumn()).getValues(); // 検索にヒットしたセルに背景色を付与 for (let j = 0; j < hitRanges.length; j++) { if (hitRanges[j][0] == targetRows[i]) { data[0][hitRanges[j][1]-1] = '<div style="background-color:#EDF7FF;">' + data[0][hitRanges[j][1]-1] + '</div>'; } } myData.push(data[0]); } } // htmlを生成 let template = HtmlService.createTemplateFromFile("index"); template.myData = myData; return template.evaluate();
コード.gsの全量
まとめとして、コード.gsの全量を載せておきます。
function doGet() { let myData = SpreadsheetApp.getActiveSheet().getDataRange().getValues(); let template = HtmlService.createTemplateFromFile("index"); template.myData = myData; return template.evaluate(); } function doPost(e) { let searchW = e.parameter.search; // 検索ワードの取得 let sheet = SpreadsheetApp.getActiveSheet(); let myData = []; // 検索にヒットしたデータの格納先配列 if (searchW == null || searchW == '') { myData = SpreadsheetApp.getActiveSheet().getDataRange().getValues(); // 検索ワードがnullの場合は全件取得 } else { let ranges = sheet.createTextFinder(searchW).findAll(); // キーワードによる検索を実施 let targetRows = []; // 検索にヒットしたレコード行の格納先 let hitRanges = []; // 検索にヒットしたセルのRangeの格納先 // 検索にヒットしたRangeとレコード行を格納 for (let i = 0; i < ranges.length; i++) { targetRows.push(ranges[i].getRow()); hitRanges.push([ranges[i].getRow(), ranges[i].getColumn()]); } // 1行に複数個ヒットした場合の重複行番号排除 targetRows = targetRows.filter(function(x, i, self) { return self.indexOf(x) === i; }); for (let i = 0; i < targetRows.length; i++) { // 検索にヒットしたレコードの取得 let data = sheet.getRange(targetRows[i], 1, 1, sheet.getLastColumn()).getValues(); // 検索にヒットしたセル値に背景色を付与 for (let j = 0; j < hitRanges.length; j++) { if (hitRanges[j][0] == targetRows[i]) { data[0][hitRanges[j][1]-1] = '<div style="background-color:#EDF7FF;">' + data[0][hitRanges[j][1]-1] + '</div>'; } } myData.push(data[0]); } } // htmlを生成 let template = HtmlService.createTemplateFromFile("index"); template.myData = myData; return template.evaluate(); }
htmlファイルの作成
GASの実装は終わったので、今度はWebに表示するhtmlファイルを作成していきます。
GASで設定していたように、ファイル名は”index.html”とします。
「ファイル」から「HTML」をクリック。
「index」と打ち込んでEnterすると、index.htmlができます。
では早速以下にコードを記載します。
全量としてはこのようになります。基本的にはGASから渡された”myData”パラメータを順に読み込み、テーブルとして組み立てるだけのシンプルなものです。
<!DOCTYPE html> <html> <head> <base target="_top"> </head> <body> <form method="post" action="https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec"> 検索:<input type="text" name="search" /> <input type="submit" value="検索" /><br /> <table border=1> <? for (var i = 0; i < myData.length; i++) { ?> <tr> <? for (var j = 0; j < myData[i].length; j++) { ?> <td> <? output._=(myData[i][j] + '<br/>'); ?> </td> <? } ?> </tr> <? } ?> </table> </form> </body> </html>
formタグのaction属性に記載するURLは、このアプリ固有のURLになります。
デプロイ時に固有のURLが割り当てられるので、それをコピペして貼り付けておきます。
起動・動作確認
では作成したアプリを動かしてみましょう。
アプリを起動するには、スクリプトエディタの右上辺りにある「デプロイ」ボタンをクリックします。
ここでは特に公開する予定はないので、実行するユーザは「自分」で、アクセスするユーザも「自分のみ」としておきます。
いろいろとアクセス許可を求められた後、こちらの画面が表示されます。
表示されたURLをクリックすると、冒頭で示した画面が立ち上がります。