はじめに
この記事は「Ateam LifeDesign Advent Calendar 2023」で完走賞を狙って25記事書いているうちの22日目の記事です。今年も完走目指して頑張るぞ!
作るもの
昨日まででGASで簡単なウェブサイトを作って見た目を整えていきました。ウェブサイトを作る際にフォームの部分にはほぼ100%バリデーションが必要になってくるので今日はバリデーション処理を追加していきます。
追加するバリデーションは以下の2種類を実装していきます。
- 「顔文字」の必須バリデーション
- 「くだもの」の重複バリデーション(過去に同じものが投稿されている場合は投稿できない)
サンプルコード
処理としてはPOSTをされたときにバリデーションの判定を行うためdoPost関数の先頭に処理を追加していきます。
function doPost(e) {
+ var kao = e.parameter.kao;
+ var fruit = e.parameter.fruit;
+ var alert = {'kao':'', 'fruit': ''};
// 以下処理略
}
まずこの部分でエラー文言が入る変数をalert
という名前で用意しておきます。顔文字に関するエラーとくだものに関するエラーが入るように連想配列で準備します。
必須バリデーション
function doPost(e) {
var kao = e.parameter.kao;
var fruit = e.parameter.fruit;
var alert = {'kao':'', 'fruit': ''};
var values = {'kao': kao, 'fruit': fruit};
+ if (kao == '') {
+ alert['kao'] = '顔文字は必ず入力してください';
+ return createIndexHtml(alert, values);
+ }
// 以下処理略
}
+function createIndexHtml(alert = {'kao':'', 'fruit': ''}) {
+ const items = sheet.getDataRange().getValues();
+ var template = HtmlService.createTemplateFromFile("index");
+ template.deployURL = ScriptApp.getService().getUrl();
+ template.items = items;
+ template.alert = alert;
+ return template.evaluate();
+}
顔文字の必須バリデーションは上述の通りです。存在の判定でよいのでif文で空文字か否かを判定し、空だったときはalert['kao']
に対してエラー文言をセットします。その後index.html
を表示するのですがここの関数が以前用意したdoGet
関数の中身とほぼ同じ内容になるため関数として切り出しておきます。doGet
関数のとの違いはtemplate.alert
を使ってエラー内容をテンプレート側に渡す処理を追加している点になります。
同様に重複バリデーションも作っていきます。
重複バリデーション
重複バリデーションは先程のように空文字との比較みたいに単純にはできないので、まず与えられたくだもの名がすでに存在するか否かを判定するcheckDuplicatedFruit
という関数をつくっておきます。
function checkDuplicatedFruit(fruit) {
var lastRow = sheet.getLastRow();
var data = sheet.getRange('C1:C' + lastRow).getValues();
for (var i = 0; i < data.length; i++) {
if (data[i][0] === fruit) {
return true; // 同じくだものが見つかった場合はtrueを返す
}
}
return false; // 同じくだものが見つからなかった場合はfalseを返す
}
この関数ではC列を最終行まで見て行ってそれらのセルに書き込まれている値をdata
変数に保持しておきます。あとはその変数を1つずつ見ていきながら与えられたくだもの名と同じものがあれば重複があったということでtrue
を、最後まで同じものがみつからなければfalse
を返すようにしています。
この関数を使って先程のdoPost
関数に処理を追加すると
function doPost(e) {
var kao = e.parameter.kao;
var fruit = e.parameter.fruit;
var alert = {'kao':'', 'fruit': ''};
if (kao == '') {
alert['kao'] = '顔文字は必ず入力してください';
return createIndexHtml(alert);
}
+ if (checkDuplicatedFruit(fruit)) {
+ alert['fruit'] = 'くだものがすでに登録されているものと重複しています';
+ return createIndexHtml(alert);
+ }
// 以下処理略
}
これによってすでに登録があるくだものの場合は先程同様エラー文言を渡すようにします。
では次にこのエラー文言をサイト側で表示するためにindex.html
を編集していきます。
index.htmlにエラー内容を表示
<div class="col">
<label for="kao" class="form-label">顔文字</label>
- <input type="text" class="form-control" id="kao" name="kao">
+ <input type="text" class="form-control <?= alert['kao'] ? 'is-invalid' : '' ?>" id="kao" name="kao">
+ <div class="invalid-feedback">
+ <?= alert['kao'] ?>
+ </div>
</div>
<div class="col">
<label for="fruit" class="form-label">くだもの</label>
- <input type="text" class="form-control" id="fruit" name="fruit">
+ <input type="text" class="form-control <?= alert['fruit'] ? 'is-invalid' : '' ?>" id="fruit" name="fruit">
+ <div class="invalid-feedback">
+ <?= alert['fruit'] ?>
+ </div>
</div>
Bootstrapのバリデーションエラー表示のためのCSSを使っています。エラー内容自体はinvalid-feedback
というクラスを振ったdiv
内に表示するようにし、同列にある兄弟要素のinput
タグにエラーがある場合はis-invalid
というクラスを追加します。これによりフォームのinput部分が赤枠で囲われてエラー文言が表示されるようになります。
is-invalid
クラスを追加せずinvalid-feedback
のみを追加した場合、エラー文言が表示されないので注意してください。
と、これで完成したように思えるのですが、このままだとエラーが発生した場合、前に入力していた内容が保持されません。再度すべて入力するようにさせるのはユーザビリティが悪いので入力内容を保持できるように処理を追加していきます。
入力内容の保持
function doPost(e) {
var kao = e.parameter.kao;
var fruit = e.parameter.fruit;
var alert = {'kao':'', 'fruit': ''};
+ var values = {'kao': kao, 'fruit': fruit};
if (kao == '') {
alert['kao'] = '顔文字は必ず入力してください';
- return createIndexHtml(alert);
+ return createIndexHtml(alert, values);
}
if (checkDuplicatedFruit(fruit)) {
alert['fruit'] = 'くだものがすでに登録されているものと重複しています';
- return createIndexHtml(alert);
+ return createIndexHtml(alert, values);
}
// 以下処理略
}
まずdoPost
関数内で受け取ったvalues
をhtmlを生成する関数に第2引数として渡してやります。
-function createIndexHtml(alert = {'kao':'', 'fruit': ''}) {
+function createIndexHtml(alert = {'kao':'', 'fruit': ''}, values = {'kao':'', 'fruit': ''}) {
const items = sheet.getDataRange().getValues();
var template = HtmlService.createTemplateFromFile("index");
template.deployURL = ScriptApp.getService().getUrl();
template.items = items;
template.alert = alert;
+ template.values = values;
return template.evaluate();
}
createIndexHtml
関数側でも第2引数としてvalues
を受け取れるようにしたあと、テンプレート側にその値を引き渡すようにします。
<div class="col">
<label for="kao" class="form-label">顔文字</label>
- <input type="text" class="form-control <?= alert['kao'] ? 'is-invalid' : '' ?>" id="kao" name="kao">
+ <input type="text" class="form-control <?= alert['kao'] ? 'is-invalid' : '' ?>" id="kao" name="kao" value="<?= values['kao'] ?>">
<div class="invalid-feedback">
<?= alert['kao'] ?>
</div>
</div>
<div class="col">
<label for="fruit" class="form-label">くだもの</label>
- <input type="text" class="form-control <?= alert['fruit'] ? 'is-invalid' : '' ?>" id="fruit" name="fruit">
+ <input type="text" class="form-control <?= alert['fruit'] ? 'is-invalid' : '' ?>" id="fruit" name="fruit" value="<?= values['fruit'] ?>">
<div class="invalid-feedback">
<?= alert['fruit'] ?>
</div>
</div>
そしてテンプレート側で受け取った値をinput
フォームのvalue
属性にしていすることで、受け取った値がページを読み込んだ段階で表示されるようにします。
完成品
コード
全体はこんな感じになりました。
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName('Sheet1'); // シート名を入力
function doGet() {
return createIndexHtml();
}
function createIndexHtml(alert = {'kao':'', 'fruit': ''}, values = {'kao':'', 'fruit': ''}) {
const items = sheet.getDataRange().getValues();
var template = HtmlService.createTemplateFromFile("index");
template.deployURL = ScriptApp.getService().getUrl();
template.items = items;
template.alert = alert;
template.values = values;
return template.evaluate();
}
function doPost(e) {
var kao = e.parameter.kao;
var fruit = e.parameter.fruit;
var alert = {'kao':'', 'fruit': ''};
var values = {'kao': kao, 'fruit': fruit};
if (kao == '') {
alert['kao'] = '顔文字は必ず入力してください';
return createIndexHtml(alert, values);
}
if (checkDuplicatedFruit(fruit)) {
alert['fruit'] = 'くだものがすでに登録されているものと重複しています';
return createIndexHtml(alert, values);
}
var currentDate = new Date();
const newRow = [
currentDate.toLocaleDateString(),
kao,
fruit
];
sheet.appendRow(newRow);
var template = HtmlService.createTemplateFromFile("complete");
return template.evaluate();
}
function checkDuplicatedFruit(fruit) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lastRow = sheet.getLastRow();
var data = sheet.getRange('C1:C' + lastRow).getValues();
for (var i = 0; i < data.length; i++) {
if (data[i][0] === fruit) {
return true; // 同じフルーツが見つかった場合はtrueを返す
}
}
return false; // 同じフルーツが見つからなかった場合はfalseを返す
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
<div class="container" style="max-width: 1000px;">
<h1>簡単なサイトを作るよ。</h1>
<table class="table table-striped">
<thead>
<tr><th>日付</th><th>顔</th><th>食べ物</th></tr>
</thead>
<tbody>
<? for(let i = 1; i < items.length; i++){ ?>
<? let item = items[i]; ?>
<tr><td><?= item[0] ?></td><td><?= item[1] ?></td><td><?= item[2] ?></td></tr>
<? } ?>
</tbody>
</table>
<form method="post" action="<?= deployURL ?>" class="mt-4">
<div class="mb-3 row">
<div class="col">
<label for="kao" class="form-label">顔文字</label>
<input type="text" class="form-control <?= alert['kao'] ? 'is-invalid' : '' ?>" id="kao" name="kao" value="<?= values['kao'] ?>">
<div class="invalid-feedback">
<?= alert['kao'] ?>
</div>
</div>
<div class="col">
<label for="fruit" class="form-label">くだもの</label>
<input type="text" class="form-control <?= alert['fruit'] ? 'is-invalid' : '' ?>" id="fruit" name="fruit" value="<?= values['fruit'] ?>">
<div class="invalid-feedback">
<?= alert['fruit'] ?>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">送信</button>
</form>
</div>
</body>
</html>
顔文字の必須バリデーションもくだものの重複バリデーションもきちんと動いていることが確認できました。
最後に
今日はGASで作ったフォームにバリデーション処理を追加する方法をみていきました。必須や重複チェック以外にも、在庫管理システムであれば在庫が発注数以上あるかなどのチェックや、単純なメールアドレス、電話番号のフォーマットチェックなど様々な処理を追加することができます。ぜひ色々試してみてください!