96
127

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GoogleAppsScript(GAS)で入力フォームを作る

Last updated at Posted at 2016-12-11

概要

  • Google Apps Scriptsのウェブアプリケーション
    • スプレッドシートの参照、更新、メール送信ができるため、現場でちょっとしたツールを作るのに向いてる
  • gasでウェブアプリケーションとして導入するまでの手順は省略
  • 使いまわせるような雛形を作っておき、後でコピペで作れるようにしておくことを目的としてます

リンク

サンプルプログラムの内容

スクリーンショット 2016-12-11 21.06.19.png

  • 入力フォームの内容をスプレッドシートに追記する
  • 入力フォームのセレクトボックス(動的)は、マスタデータを記載しているスプレッドシートから動的に生成する

使用ライブラリ

  • jquery(あまり使いたくないけど、以下のライブラリ使うのに必要なため)
  • bootstrap(デザイン)
  • fontawesome(フォント)
  • toastr(トースト通知)
  • bootstrap-select(高機能セレクトボックス)
  • bootstrap-validator(入力チェック)

ソース

  • コード.js :GETリクエストきたときにindex.html表示。スプレッドシート参照・更新処理。
  • index.html:入力フォーム画面のhtmlを記述。
  • javascript.html:javascriptを記述。index.htmlから外だししておく。
  • stylesheet.html:スタイルシートを記述。index.htmlから外だししておく。

コード.js

  • doGet():GETリクエストがきたら、index.htmlを返す。javascriptとスタイルシートを外だしするために、HtmlService.createTemplateFromFile()を使用する。
    - registerSSByFormData():入力フォームデータを受け取って、スプレッドシートを更新する。
  • getSelectListFromMasterSS():スプレッドシートを参照し、入力フォームのセレクトボックスを動的に作成する。
function doGet() {
  return HtmlService.createTemplateFromFile('index')
    .evaluate()
    .setSandboxMode(HtmlService.SandboxMode.IFRAME)
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setTitle('gas入力フォームサンプル');
}

function registerSSByFormData(data) {
  
  Logger.log("data = %s", data);
  
  var datasheet = SpreadsheetApp.openById('送信データを登録するスプレッドシートのID').getSheetByName('シート名');
  var now = new Date();

  var i = datasheet.getLastRow() + 1;
  datasheet.getRange(i,  1).setValue(data[ 1]);
  datasheet.getRange(i,  2).setValue(data[ 2]);
  datasheet.getRange(i,  3).setValue(data[ 3]);
  datasheet.getRange(i,  4).setValue(data[ 4]);
  datasheet.getRange(i,  5).setValue(data[ 5]);
  datasheet.getRange(i,  6).setValue(data[ 6]);
  datasheet.getRange(i,  7).setValue(data[ 7]);
  datasheet.getRange(i,  8).setValue(data[ 8]);
  datasheet.getRange(i,  9).setValue(data[ 9]);
  datasheet.getRange(i, 10).setValue(data[10]);
  datasheet.getRange(i, 11).setValue(data[11]);
  datasheet.getRange(i,  12).setValue(Utilities.formatDate(now, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss'));
  result = true;

  return {data: true};
}

function getSelectListFromMasterSS() {
  var selectList = [];
  
  // マスタデータシートを取得
  var datasheet = SpreadsheetApp.openById('マスタデータのスプレッドシートのID').getSheetByName('シート名');
  // B列2行目のデータからB列の最終行までを取得 
  var lastRow = datasheet.getRange("B:B").getValues().filter(String).length - 1;
  Logger.log("lastRow = %s", lastRow);
  // B列2行目のデータからB列の最終行までを1列だけ取得 
  selectList = datasheet.getRange(2, 2, lastRow, 1).getValues();
  Logger.log("selectList = %s", selectList); 

  return {data: selectList};
}

index.html

  • HtmlService.createHtmlOutputFromFile()を使い、外だししてあるjavascript.htmlと、stylesheet.htmlをincludeする。
  • デザインにはbootstrapを使用
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <?!= HtmlService.createHtmlOutputFromFile('stylesheet').getContent(); ?>
</head>
<body>

  <nav class="navbar navbar-inverse">
    <div class="navbar-header"><a class="navbar-brand" href="#">入力フォーム</a></div>
  </nav>
  
<div class="container">
    <form class="form-horizontal" id="myForm" onsubmit="return false;">
      
        <div class="form-group">
            <label class="control-label col-xs-3" for="input_id1">氏名</label>
            <div class="col-xs-9">
                <input type="text" class="form-control" id="input_id1" required>
                <div class="help-block with-errors"></div>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-xs-3" for="input_id2">パスワード</label>
            <div class="col-xs-9">
                <input type="password" class="form-control" id="input_id2" required>
                <div class="help-block with-errors"></div>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-xs-3">チェックボックス</label>
            <div class="col-xs-9">
                <div class="checkbox" id="checkbox1">
                    <label><input type="checkbox" id="input_id3" >check1</label>
                    <label><input type="checkbox" id="input_id4" >check2</label>
                    <label><input type="checkbox" id="input_id5" >check3</label>
                </div>
                <div class="help-block with-errors"></div>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-xs-3">ラジオボタン</label>
            <div class="col-xs-9">
                <div class="radio" id="radiobutton1">
                    <label><input type="radio" name="radio1" id="input_id6" required>radio11</label>
                    <label><input type="radio" name="radio1" id="input_id7" required>radio12</label>
                    <label><input type="radio" name="radio1" id="input_id8" required>radio13</label>
                </div>
                <div class="help-block with-errors"></div>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-xs-3">セレクトボックス(search)</label>
            <div class="col-xs-9">
                <select id="select_id1" class="selectpicker form-control" data-live-search="true" width="auto">
                  <option selected>選択肢1</option>
                  <option>選択肢2</option>
                  <option>選択肢3</option>
                  <option>テストA</option>
                  <option>テストB</option>
                </select>
                <div class="help-block with-errors"></div>
            </div>
        </div>
        
        <div class="form-group">
            <label class="control-label col-xs-3">セレクトボックス(multiple)</label>
            <div class="col-xs-9">
                <select id="select_id2" class="selectpicker form-control" multiple >
                  <option selected>ガリガリ君(ソーダ味)</option>
                  <option>ハーゲンダッツ</option>
                  <option>ピノ</option>
                  <option>ガリガリ君(コーラ味)</option>
                </select>
                <div class="help-block with-errors"></div>
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-xs-3">セレクトボックス(動的)</label>
            <div class="col-xs-9">
                <select class="form-control" id="select_id3"></select>
                <div class="help-block with-errors"></div>
            </div>
        </div>
        
        <div class="form-group">
            <div class="col-xs-offset-3 col-xs-9">
                <button id="send_button" type="submit" class="btn btn-primary btn-large">
                  <i class="fa fa-send fa-lg"></i>送信
                </button>
            </div>
        </div>
    </form>
<i id="processing" class="fa fa-refresh fa-spin fa-3x fa-fw"></i>
</div>
<?!= HtmlService.createHtmlOutputFromFile('javascript').getContent(); ?>
</body>
</html>

stylesheet.html

  • CDNのスタイルシートを参照
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.12.1/css/bootstrap-select.min.css">

javascript.html

  • 処理内容はコメントに記載の通り。
  • コード.gs側のfunctionを呼び出しするのにgoogle.script.run.withSuccessHandlerを使用します。
  • bootstrap-validatorのバリデーションはsubmitしないとかからないため小細工してます
    • フォームsubmitで、submit用のボタンのスタイルがdisabledだったら、バリデーションエラーなので、送信処理はしないようにする
    • フォームsubmitで、画面遷移させないように、onsubmit="return false;"にしておく
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://use.fontawesome.com/7bcbed1321.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.12.1/js/bootstrap-select.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/1000hz-bootstrap-validator/0.11.8/validator.min.js"></script>
<script>
/**
 コード.jsのgetSelectListFromSS()を読んで、スプレッドシートのデータをもとに、セレクトボックスを構築する
*/
function getSelectList() {
    try {
        google.script.run
            .withSuccessHandler(function(result) {

                console.log("result", result);

                var select = document.getElementById('select_id3');
                var option = document.createElement('option');
                option.setAttribute('value', '');
                option.setAttribute('selected', true);
                option.innerHTML = '未選択';
                select.appendChild(option);

                for (var i = 0; i < result.data.length; i++) {
                    option = document.createElement('option');
                    option.setAttribute('value', i + 1);
                    option.innerHTML = result.data[i];
                    select.appendChild(option);
                }


            })
            .withFailureHandler(function(result) {
                toastr.error('リストの取得に失敗しました。', result);
            })
            .getSelectListFromMasterSS();

    } catch (e) {
        toastr.error(e);
    }
}

/**
 コード.jsのregisterSSByFormData()を呼んで、フォームの内容をスプレッドシートに登録する
*/
function sendData() {

    try {

        processing(true);

        var elemntIFrame = window.parent.document.getElementById('userHtmlFrame');
        elemntIFrame.setAttribute('scrolling', 'no');
        elemntIFrame.setAttribute('frameborder', '0');

        console.log("document.getElementById('select_id2').selectedIndex)", document.getElementById('select_id2').selectedIndex);
        
        var data = [];
        data[0] = 'dummy';
        data[1] = document.getElementById('input_id1').value;
        data[2] = document.getElementById('input_id2').value;
        data[3] = document.getElementById('input_id3').checked;
        data[4] = document.getElementById('input_id4').checked;
        data[5] = document.getElementById('input_id5').checked;
        data[6] = document.getElementById('input_id6').checked;
        data[7] = document.getElementById('input_id7').checked;
        data[8] = document.getElementById('input_id8').checked;
        data[9] = document.getElementById('select_id1').options[document.getElementById('select_id1').selectedIndex].text;
        data[10] = getMultipleSelectedText(document.getElementById('select_id2').options);
        data[11] = document.getElementById('select_id3').options[document.getElementById('select_id3').selectedIndex].text;

        console.log("data", data);
        
        
        google.script.run
            .withSuccessHandler(function(result) {
                processing(false);
                if (result.data) {
                    toastr.info('送信完了');
                } else {
                    toastr.error('送信失敗しました');
                }
            })
            .withFailureHandler(function(result) {
                processing(false);
                toastr.error('データ送信に失敗しました。', result);
            })
            .registerSSByFormData(data);
        
    } catch (e) {
        processing(false);
        toastr.error(e);
    }
    
}

// multipeセレクトをカンマ区切りのテキストにして取得
function getMultipleSelectedText(selectOptions){
  console.log(selectOptions, selectOptions.length);
  var arr = [];
  for(var i = 0; i < selectOptions.length; i++) {
    if(selectOptions[i].selected){
      arr.push(selectOptions[i].value);
    }
  }
  return arr.join(',');
}

// 処理中アイコン表示・非表示
function processing(processing) {
    if (processing) {
        document.getElementById('processing').style.visibility = "visible";
        document.getElementById('send_button').setAttribute("disabled", true);
    } else {
        document.getElementById('processing').style.visibility = "hidden";
        document.getElementById('send_button').removeAttribute("disabled");
    }
}

// ロード完了後の処理
$(function() {
    // トースト通知の位置指定
    toastr.options.positionClass = "toast-bottom-left";
    // バリデーションチェック有効化
    $('#myForm').validator();
    // 処理中アイコン非表示
    processing(false);
    // セレクトボックスのリストを動的に取得する
    getSelectList();

    // submit時のイベント登録
    $('#myForm').validator().on('submit', function(e) {
        if (document.getElementById('send_button').className.indexOf("disabled") != -1) {
            // submit用のボタンのスタイルにdisabledが設定されていればバリデーションエラー
        } else {
            // バリデーションエラーでなければ送信
            sendData();
        }
    })
});
</script>
96
127
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
96
127

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?