GASでWebアプリを作成する!
◆目的
・簡単なWebアプリ作成でサーバーサイド、フロントエンドの切り分け・情報のやり取りを理解する
・Webアプリに様々な入力形式を作成して、各入力形式のJSでの扱い方を学ぶ
※アプリの機能としてはGoogleフォームとスプレッドシートを連携すれば同じようなものを作成できる。
作成Webアプリ(リンク)
機能説明
・name, oldなど記入フォーム
・スプレッドシートに反映ボタン:記入フォーム入力内容をスプレッドシート(仮DB)に格納する
・データ再利用のプルダウン:過去格納データから再利用するデータを選択できる。
・データを再利用ボタン:プルダウンで選択した過去データが記入フォームに反映される。
・スプレッドシートのデータが反映→表示される表
注意点
今回のアプリでは記入フォームに入力した値をそのまま裏側のSSに格納。格納したデータを表で表示する形になっている。
もし記入フォームに関数が記入されるとSSに値が格納後、関数が起動してしまう。SS内で他の重要な情報など管理しているとその情報が抜き取られてしまう危険がある。
今回のフォームでは”=”を記入させないことによってそのようなリスクを回避している。
◆サーバーサイド、フロントエンド
Webアプリ、サイトなどユーザーが操作するプロダクトで使われるスクリプト・機能の切り分け方。
フロントエンド
ユーザーが実際に操作する画面を作成する
HTML, CSS, Javascriptが使われることが多い
サーバーサイド
操作画面以外(DBや他のアプリやプロダクト)と連携するような機能
今回のプロダクトではフロントエンド、サーバーサイドの両方にJSが記述される。どのようにフロントエンド、サーバーサイドのJSを切り分ければいいのかを下記で記述する。
フロントエンドのJS
index.htmlの<script>タグ内に記述されている内容がフロントエンドのJSにあたる。ここでは基本HTML(表示)に対する操作とサーバーサイドの関数の呼び出し、情報の受け渡しを行う。
サーバーサイドのJS
script.gs内のスクリプトがサーバーサイドのJSに相当する。フロントエンド以外の処理(DBや他アプリとの連携)はこちらに記述する。
◆アプリ作成時に詰まった点
・google.script.run.withSuccessHandler(コールバック関数).関数A();
上記のスクリプトは関数A() を実行した後に関数A() で設定された戻り値を引数としてコールバック関数が実行される。
関数A() からコールバック関数に引数としてarrayデータは渡せない。今回のようにスプレッドシートの配列データを取得したい場合はJSON.stringify()、JSON.parse() を使用して文字列化する必要がある。
・「スプレッドシートに反映」クリック後のユーザー表示画面の登録データ表示更新。
本来なら「スプレッドシートに反映」クリック後に一番下の表のデータが更新される仕組みにしたかった。しかしフロント側のJSで更新を行う関数(location.reload())を記入しても、更新後にHTMLが再反映されずユーザー画面が白紙になってしまうエラーが起きた。
こちらは解決できず、表更新の際はF5を押してもらうようユーザーを誘導することにした。
◆スクリプト
JSファイル (script.gs)
function doGet() {
//Webアプリの公開リンククリック時に実行される。
// 表示したいHTMLのファイル名を指定(index.htmlファイルを呼び出して表示する)
return HtmlService.createTemplateFromFile("index").evaluate();
}
function getSheet(name){
//スプレッドシートのシート取得関数
// SSIDからスプレッドシートの取得
var ssId = 'SS_id';
var ss = SpreadsheetApp.openById(ssId);
// 指定されたシート名からシートを取得して返却
var sheet = ss.getSheetByName(name);
return sheet;
}
function getData() {
// 指定したシートから全データを取得する関数
var values = getSheet('入力データ').getDataRange().getValues();
return values;
}
function getData2() {
// 指定したシートから全データを取得する関数
// フロントエンドにデータを渡すため返り値をJSON化している
var values = getSheet('入力データ').getDataRange().getValues();
return JSON.stringify(values);
}
function setEvent(set_data) {
//「スプレッドシートに反映」ボタンクリック時に呼び出され、スプレッドシートに入力フォームのデータを格納する関数
var sheet = getSheet('入力データ');
var lastRow = sheet.getLastRow();
// 最終行にデータ挿入
sheet.appendRow([lastRow,set_data[1],new Date(),set_data[0],set_data[2],set_data[3].join(',')]);
}
HTMLファイルスクリプト(index.html)
<!DOCTYPE html>
<html>
<head>
<base target="_top">
//cssの記述
<style>
table{
border: solid 1px #000000;
border-collapse: collapse;
}
th {border: solid 1px #000000}
td {border: solid 1px #000000}
</style>
//javascriptの記述
<script type="text/javascript">
//スプレッドシート側からデータを取得する
google.script.run.withSuccessHandler(onSuccess).getData2();
//取得したデータでドロップダウンメニューを作って設置
function onSuccess(data){
var data_arr = JSON.parse(data);
console.log("稼働");
console.log(data_arr);
//ラベルを入れる
var html = "<label>再利用するデータを選択:</label>";
//selectタグの頭を入れる
html += "<select title='プルダウンより予定を選択' id='copy_pulldown'><option>再利用データを選択して下さい</option>";
//HTMLデータの生成
for(var i = 1;i<data_arr.length;i++){
//オプション項目を追加
html += "<option>ID:" + data_arr[i][0] + "name:"+ data_arr[i][3] + "</option>"
}
//selectタグの下を入れる
html += "</select><p>";
console.log("稼働");
console.log(html);
//プルダウンメニューを設置する
document.getElementById("selectedEvents").innerHTML = html;
}
function reflectSpreadsheet(){
//スプレッドシートに入力フォームのデータを取得
let set_data =[];
set_data.push(document.getElementById("name").value);
set_data.push(document.getElementById("old").value);
if(document.getElementById("gender1").checked){set_data.push(document.getElementById("gender1").value);}
if(document.getElementById("gender2").checked){set_data.push(document.getElementById("gender2").value);}
let animal_arr =[];
if(document.getElementById("animal1").checked){animal_arr.push(document.getElementById("animal1").value);}
if(document.getElementById("animal2").checked){animal_arr.push(document.getElementById("animal2").value);}
if(document.getElementById("animal3").checked){animal_arr.push(document.getElementById("animal3").value);}
set_data.push(animal_arr);
console.log(set_data);
for(var i=0;i<set_data.length;i++){
if(set_data[i]===""){
alert("記入欄に空欄が存在します");
return;
}
if(set_data[i].indexOf("=")){
alert("記入欄に関数を記入しないでください");
return;
}
}
//スプレッドシートにデータ格納するためサーバーサイドのset_data関数を呼び出し
google.script.run.setEvent(set_data);
//フォーム記入項目の値のクリア
document.getElementById("name").value = "";
document.getElementById("old").value = "";
document.getElementById("gender1").checked = false;
document.getElementById("gender2").checked = false;
document.getElementById("animal1").checked = false;
document.getElementById("animal2").checked = false;
document.getElementById("animal3").checked = false;
}
//copy_data,data_in関数で「データ再利用」ボタンを作成
//プルダウンで選んだデータが入力フォームに再記入されるようになっている
function copy_data(){
//スプレッドシート側からデータを取得する
google.script.run.withSuccessHandler(data_in).getData2();
}
function data_in(data){
var data_arr = JSON.parse(data);
//プルダウンdata取得
var choiced = document.getElementById("copy_pulldown").value;
let copy_data = data_arr.filter(row => "ID:" + row[0] + "name:"+ row[3] ===choiced);
console.log(copy_data);
document.getElementById("name").value = copy_data[0][3];
document.getElementById("old").value = copy_data[0][1];
if(copy_data[0][4]==="男性"){
document.getElementById("gender1").checked = true ;
}else if(copy_data[0][4]==="女性"){
document.getElementById("gender2").checked = true ;
}
if(copy_data[0][5].indexOf("犬")!=-1){document.getElementById("animal1").checked = true ;}
if(copy_data[0][5].indexOf("猫")!=-1){document.getElementById("animal2").checked = true ;}
if(copy_data[0][5].indexOf("うさぎ")!=-1){document.getElementById("animal3").checked = true ;}
}
</script>
</head>
<body>
<h2>GASによる入力フォーム</h2>
<form method="post" action="公開しているWebアプリのURL">
<label>name:<input type="text" name="name" id="name"></label><br>
<label>old:<input type="text" name="old" id="old"></label><br>
性別:
<label><input type="radio" name="gender" id="gender1" value="男性">男性</label>
<label><input type="radio" name="gender" id="gender2" value="女性">女性</label>
<br>
好きな動物:
<label><input type="checkbox" name="animal" id="animal1" value="犬">いぬ</label>
<label><input type="checkbox" name="animal" id="animal2" value="猫">ねこ</label>
<label><input type="checkbox" name="animal" id="animal3" value="うさぎ">うさぎ</label>
<br><br>
<button type="button" onclick="reflectSpreadsheet();" class="btn-border">スプレッドシートに反映</button><br><br><br><br>
</form>
<!-- プルダウン設置場所 -->
<div id="selectedEvents">
<img border="0" src="https://officeforest.org/wp/library/ProgressSpinner.gif" width="20" height="20">
</div>
<button type="button" onclick="copy_data();" class="btn-copy">データを再利用</button><br><br><br>
<h3>※表示を更新するにはF5を押してください</h3>
<table>
<?
// スプレッドシートからデータを取得
var data = getData();
// テーブルの見出し作成
output._=('<tr>');
output._=('<th>' + data[0][0] + '</th>');
output._=('<th>' + data[0][1] + '</th>');
output._=('<th>' + data[0][2] + '</th>');
output._=('<th>' + data[0][3] + '</th>');
output._=('<th>' + data[0][4] + '</th>');
output._=('<th>' + data[0][5] + '</th>');
output._=('</tr>');
// テーブルを作成
for(var i=1;i<data.length;i++){
output._=('<tr>');
output._=('<td>' + data[i][0] + '</td>');
output._=('<td>' + data[i][1] + '</td>');
output._=('<td>' + data[i][2] + '</td>');
output._=('<td>' + data[i][3] + '</td>');
output._=('<td>' + data[i][4] + '</td>');
output._=('<td>' + data[i][5] + '</td>');
output._=('</tr>');
}
?>
</table>
</body>
</html>