GASで作ったWEBフォームが上手く動かない
実現したいこと
全くの初心者です。
学校でトラブルなどが起きた内容や対応を記録するフォームがつくりたいです。
- 学年プルダウンを選択すると、児童プルダウンに当該学年の児童名が格納される
- 児童は追加ボタンで何名でも追加できる。×ボタンで不要なプルダウンは消せる
- モーダルで確認後、登録ボタンでスプレッドシートに記録される
前提
GASを使って、学校でトラブルが起きた際に日付、関係児童、概要、対応、保健室利用、レベルを記録するためのフォームを作っています。コードを書くことは全くの素人です。Googleフォームを使わなかったのは、関係児童をプルダウンで選べて、何名でも追加できるようにしたかったからです。
発生している問題・エラーメッセージ
児童を追加ボタンをクリックすると、児童選択selectのcloneが一瞬表示されたあと、フォーム画面が真っ白になります。
該当のソースコード
// スプレッドシートID
const SPREADSHEET_ID = "*当該シートのIDが入っています";
// HTMLファイル名
const HTML_FILE = "index";
// 児童名簿シート名
const ROSTER_SHEET = "児童名簿";
// 記録シート名
const RECORD_SHEET = "生徒指導情報ファイル";
// HTMLファイルを表示する関数
function doGet() {
var html = HtmlService.createTemplateFromFile(HTML_FILE).evaluate();
html.setTitle("生徒指導情報ファイル");
return html;
}
// HTMLのURLを取得する
function getAppUrl(){
return ScriptApp.getService().getUrl();
}
// 学年に対応する氏名の配列を返す関数
function getStudents(grade) {
// スプレッドシートを開く
const app = SpreadsheetApp.openById(SPREADSHEET_ID);
// シートを取得する
const sheet = app.getSheetByName(ROSTER_SHEET);
// 全データを取得する
const values = sheet.getDataRange().getValues();
// 氏名の配列を作る
const students = [];
for(let i=0; i<values.length; i++){
// 最初の行はヘッダーなのでスキップする
if(i === 0)continue;
// 学年が一致したら氏名を追加する
if(values[i][0] == grade){
students.push(values[i][1]);
}
}
// 配列を返す
return students;
}
//スプレッドシートにメニューを追加する
function onOpen(){
var ui = SpreadsheetApp.getUi();
ui.createMenu('👦生徒指導情報ファイル👧')
.addItem('📧報告フォーム', 'htmlbox')
.addToUi();
}
//生徒指導報告フォームを表示する
function htmlbox() {
var output = HtmlService.createTemplateFromFile('index');
var ss = SpreadsheetApp.getActiveSpreadsheet();
var html = output.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(1500)
.setHeight(600);
ss.show(html); //メッセージボックスとして表示する
}
function doSubmitAjax(req) {
const params = req.parameters;
const resObj = {};
insertRecord(params);
return resObj;
}
const USER_ID =Session.getActiveUser().getEmail();
function insertRecord(param){
//この順番にスプレッドシートに格納される
const data = [[
new Date(),
USER_ID,
param.calendar_date,
param.name,
param.summary,
param.response,
param.nurse,
param.level,
]];
const app = SpreadsheetApp.openById(SPREADSHEET_ID);
const sheet = app.getSheetByName(RECORD_SHEET);
const insertRow = sheet.getDataRange().getLastRow() + 1; //挿入行
const insertCol = 1; //挿入列
const insertRowNum = data.length; //挿入行数
const insertColNum = data[0].length; //挿入列数(データ数)
const insertRange = sheet.getRange(insertRow, insertCol,insertRowNum,insertColNum);
insertRange.setValues(data);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
<link rel ="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.js"></script>
<title>生徒指導情報ファイル</title>
</head>
<body class="is-flex is-justify-content-center has-background-light">
<div class="main has-background-white is-flex-direction-column">
<!--フォームタイトル-->
<div class="conteiner form-title">
<section class="hero is-info">
<div class="hero-body">
<div class="conteiner">
<h1 class="title">
生徒指導報告フォーム
</h1>
</div>
</div>
</section>
</div>
<!--フォームボディ-->
<div id="registerContent" class="conteiner form-body">
<form class="box" id="seishiForm" action="<?= getAppUrl() ?>" method="post">
<!--日時欄 -->
<div class="field" id="date">
<label for="calendar_date" class="label">日時</label>
<div class="control">
<input id="calendar_date" class="input" type="date" name="calendar_date">
</div>
</div>
<!--関係児童欄 -->
<div id="student" class="field">
<label for="selectstudent" class="label">関係児童</label>
<div id="selectstudent" class="field has-addons">
<p class="control">
<span class="select">
<select name="grade" class="input">
<option value="">学年</option>
<? for (var i = 1; i <= 6; i++) {?>
<option value="<?= i ?>"><?= i ?>年生</option>
<? } ?>
</select>
</span>
</p>
<p class="control">
<span class="select">
<select name="name" class="input" disabled>
<option value="">氏名を選択してください</option>
</select>
</span>
</p>
<button id="plusButton"class="button" onclick="addStudent()">
<span class="icon is-small">
<i class="fa-solid fa-plus"></i>
</span>
<span>児童追加</span>
</button>
</div>
</div>
<!--概要欄 -->
<div class="field">
<label for="summary" class="label">概要</label><p class="help">いつ どこで だれが だれに なにをした</p>
<div class="control">
<textarea class="textarea" id="summary" name="summary" rows="3"></textarea>
</div>
</div>
<!--指導・保護者対応欄 -->
<div class="field">
<label for="response" class="label">指導・保護者対応</label><p class="help">誰が指導 謝罪は 家庭連絡は 両方か</p>
<div class="control">
<textarea class="textarea" id="response" name="response" rows="3"></textarea>
</div>
</div>
<!--保健室の利用 -->
<div id="nurse" class="field">
<label for="nursediv" class="label">保健室の利用</label>
<div class="control" id="nursediv">
<label class="radio">
<input type="radio" name="nurse" checked value="0">なし
</label>
<label class="radio">
<input type="radio" name="nurse" value="1">あり
</label>
</div>
</div>
<!--レベル -->
<div id="levelfield" class="field">
<label for="level" class="label">レベル</label><a href="" target="new" class="help">問題行動対応チャート</a>
<div class="control" id="level">
<label class="radio">
<input type="radio" name="level" checked value="1">レベル1
</label>
<label class="radio">
<input type="radio" name="level" value="2">レベル2
</label>
<label class="radio">
<input type="radio" name="level" value="3">レベル3
</label>
<label class="radio">
<input type="radio" name="level" value="4">レベル4
</label>
<label class="radio">
<input type="radio" name="level" value="5">レベル5
</label>
</div>
</div>
<!--確認ボタン-->
<div class="control">
<button type="button" id="submitButton" class="button submit-button is-info">確認画面</button>
</div>
<!--モーダル-->
<div class="modal" id="confirmModal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">確認</p>
<button type="button" class="delete cancel" aria-label="close"></button>
</header>
<section class="modal-card-body">
<table class="table is-striped is-fullwidth">
<tbody>
<tr>
<th>日付</th>
<td id="DateTd"></td>
</tr>
<tr>
<th>関係児童</th>
<td id="JidouTd"></td>
</tr>
<tr>
<th>概要</th>
<td id="SummaryTd"></td>
</tr>
<tr>
<th>指導内容・家庭連絡</th>
<td id="ShidouTd"></td>
</tr>
<tr>
<th>保健室の利用</th>
<td id="NurseTd"></td>
</tr>
<tr>
<th>レベル</th>
<td id="LevelTd"></td>
</tr>
</tbody>
</table>
</section>
<footer class="modal-card-foot">
<button type="button" id="registerButton" class="button is-success">登録</button>
<button type="button" class="button cancel">キャンセル</button>
</footer>
</div>
</div>
</form>
</div>
</div>
<script> // 学年変更で児童名格納の処理
$(function(){
// 学年プルダウンが変更されたら
$('select[name="grade"]').on("change", function(){
// 氏名プルダウンを空にする
$('select[name="name"]').prop("disabled", true);
$('select[name="name"]').empty();
$('select[name="name"]').append(`<option value="">氏名を選択してください</option>`);
// 選択された学年に対応する氏名の配列を取得する
const grade = $(this).val();
if(grade){
google.script.run.withSuccessHandler(function (data) {
for(let name of data){
$('select[name="name"]').append(`<option value="${name}">${name}</option>`);
}
// 氏名プルダウンを有効にする
$('select[name="name"]').prop("disabled", false);
}).getStudents(grade); // gradeを引数に渡す
}else{
// 氏名プルダウンを無効にする
$('select[name="name"]').prop("disabled", true);
}
});
});
</script>
<script> // 児童追加の処理
function addStudent(){
// idが"selectstudent"の<div>要素をクローンする
let clone = $("#selectstudent").clone();
// クローンした要素に連番のidを付ける
let count = $(".has-addons").length + 1;
clone.attr("id", "selectstudent" + count);
clone.find('select[name="name"]').prop("disabled",true);
clone.find("#plusButton").remove();
// クローンした要素の隣にxボタンを作る
let button = $("<button type='button' class='button xbutton'><span class='icon is-small'><i class='fa-solid fa-xmark'></i></span></button>");
button.attr("onclick", "removeStudent(this)");
clone.append(button);
// クローンした要素をidが"selectstudent"の<div>要素の下に追加する
clone.appendTo("#student");
}
// removeStudent()の定義
function removeStudent(button){
// xボタンがクリックされた要素の親要素(クローンした<div>要素)を削除する
$(button).parent().remove();
}
</script>
<script>//確認ボタンの処理(モーダル)
// JQueryの記述
$(function(){
// 確認画面ボタンをクリックしたら
$("#submitButton").on("click", function(){
const date = $('input[name="calendar_date"]').val();
const jidou = $('select[name="name"]').map(function() {return this.value}).get();
const nurse = $('input:radio[name="nurse"]:checked').val();
const level = $('input:radio[name="level"]:checked').val();
$('#DateTd').text(date);
$('#JidouTd').text(jidou);
$('#SummaryTd').html($('#summary').val().replace(/\r?\n/g, '<br/>'));
$('#ShidouTd').html($('#response').val().replace(/\r?\n/g, '<br/>'));
$('#NurseTd').text(nurse);
$('#LevelTd').text(level);
// モーダルウィンドウを表示する
$("#confirmModal").show();
});
// 閉じるボタンかキャンセルボタンをクリックしたら
$(".cancel").on("click", function(){
// モーダルウィンドウを非表示にする
$("#confirmModal").hide();
});
});
</script>
<script>//登録ボタンの処理(記録)
$(function(){
// 登録ボタンをクリックしたら
$("#registerButton").on("click", function(){
// フォームのデータを取得する
const req = {};
const params = {};
$('#seishiForm').find('input[name="level"]:checked,input[name="nurse"]:checked,input[type="date"],textarea').each(function (index, element) {
const key = $(element).attr('name');
let val = $(element).val();
params[key] = val;
});
const jidouName = $('select[name="name"]').map(function() {return this.value}).get().join('\n');
params.name = jidouName;
req.parameters = params;
console.log(params);
console.log(req);
google.script.run.withSuccessHandler(doSubmitSuccess).doSubmitAjax(req);
});
});
const doSubmitSuccess = function (result) {
// 必要な場合、成功したときの画面処理を書く
alert("送信が完了しました");
$("#confirmModal").hide();
$('#seishiForm').trigger('reset');
$("#calendar_date").focus();
};
</script>
</body>
</html>