@sakiyan2

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

GASで作ったWEBフォームが上手く動かない

実現したいこと

全くの初心者です。
学校でトラブルなどが起きた内容や対応を記録するフォームがつくりたいです。

  • 学年プルダウンを選択すると、児童プルダウンに当該学年の児童名が格納される
  • 児童は追加ボタンで何名でも追加できる。×ボタンで不要なプルダウンは消せる
  • モーダルで確認後、登録ボタンでスプレッドシートに記録される

前提

GASを使って、学校でトラブルが起きた際に日付、関係児童、概要、対応、保健室利用、レベルを記録するためのフォームを作っています。コードを書くことは全くの素人です。Googleフォームを使わなかったのは、関係児童をプルダウンで選べて、何名でも追加できるようにしたかったからです。

発生している問題・エラーメッセージ

児童を追加ボタンをクリックすると、児童選択selectのcloneが一瞬表示されたあと、フォーム画面が真っ白になります。

該当のソースコード

code.gs
// スプレッドシート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);
}
index.html
<!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>
1 likes

2Answer

現状、児童追加ボタンのクリックのデフォルト動作がsubmitになってしまっているために、ボタンをクリックするとページ遷移してしまいます。

デフォルト動作をキャンセルするには、下記のように 児童追加のボタンのイベントハンドラ(addStudent)でfalseを返すようにすればよいです。

※下記、先頭がマイナス記号の行は削除、プラス記号の行は追加

              <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()">
+              <button id="plusButton"class="button" onclick="return addStudent()">
                <span class="icon is-small">
                  <i class="fa-solid fa-plus"></i>
                </span>
                <span>児童追加</span>           
...略...
...
..

<script>  // 児童追加の処理
function addStudent(){
  // idが"selectstudent"の<div>要素をクローンする
...略...
...
..
  // クローンした要素をidが"selectstudent"の<div>要素の下に追加する
  clone.appendTo("#student");
+  // falseを返してdefault動作(submit)を回避する
+  return false;
}

これで追加ボタンを押すと白紙のページになる問題は解消します。

ただし、他にも問題点はあります。

  • 追加ボタンをクリックしたとき、選択した値がコピー先の要素に引き継がれない
  • 追加した要素の×ボタンをクリックしても、要素が削除されない
  • 追加用セレクトボックスの学年を変えると、追加済みのセレクトボックスのオプションまで変わってしまう

これらも直し方を示すことはできますが、まずは御自分で考えてみて下さい。

2Like

Comments

  1. @sakiyan2

    Questioner

    ありがとうございます!
    ナゼ問題が起きているのかを具体的に教えていただいてとても嬉しいです。

    まだ内容を理解するところまで至っていないのですが、頑張って調べてみます。

  2. @sakiyan2

    Questioner

    ヒントをいただきありがとうございました!
    少し調べて、試しに児童追加ボタンに type="button"を追加すると、意図した動きになりました。
    本当に助かりました。ありがとうございます。

丸投げに近いご質問なので、お調べするのもなかなか大変です。
まずは、GAS実行時に何かエラー等が出ていないか確認してみましたか?
ログの確認方法はこちらのページが参考になると思います。
【GAS】場面別!ログを出力・確認する方法 – はじりつ

ちょっと見たところ、addStudent()の実行までは問題なさそうなので、まずはgetAppUrl()で何か問題が起こっていないかどうかですね。

1Like

Comments

  1. @sakiyan2

    Questioner

    丸投げに近いというご指摘、ごもっともです。全くどうしていいか、何を調べればいいかも分からなかったので、詳しい人に見ていただければ、ここが間違っているよとすぐに分かることかもしれないと思い、質問させていただきました。

    早速エラーの確認方法が詳しく書かれている記事を紹介いただけて大変ありがたいです。試してみます。

Your answer might help someone💌