質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

解決済

1回答

1118閲覧

HTMLで作成したフォームのプルダウンの選択項目を、スプレッドシート上の名簿情報から自動生成したい。

morit

総合スコア1

Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

0グッド

1クリップ

投稿2021/12/14 02:35

前提・実現したいこと

プログラミング初心者です。GASを使って、学習塾の授業記録をつけるWebアプリを作成しています。
スプレッドシート上に、各講義を受けた生徒の名前をプルダウン方式で登録していく仕組みにしているのですが、
新規に入塾した生徒の名前を、ユーザー側で追加・削除できる仕組みを作りたいと考えています。

生徒の名簿情報はスプレッドシート上にあるため、これを関数で取得して、
HTMLの<select name=""></select>の中の<option value=""></option>の項目を、for文で自動追加する形にしようと思ったのですが、上手く選択項目として反映されず、困っています。

基本的な事項かとは思いますが、ご回答よろしくお願いします。m(_ _)m

js

1function tuikatest(){ 2let spread = SpreadsheetApp.openById("1YV7B_vs3L6CfwOp0zRpwKWeKuP7h68R5AB2oT3N_Ouo") 3 4 ////////////////////////////////////////////////// 5 let meibo = spread.getSheetByName('名簿'); 6 let lastRow = meibo.getLastRow(); 7 8 for (let i = 1; i <= lastRow; i++){ 9 let name = meibo.getRange(i,1).getValue(); 10 return('<option value="'+name+'">'+name+'</option>'); 11} 12 } 13

html

1生徒名1:<select name="seito1"> 2 <?= tuikatest() ?> 3 </select>

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

おそらくこういうことをなされたいのではないでしょうか。
(後述しますが、下記は非推奨です。推奨する方法については、一番下の**【代替案】を参照してください**。)
Code.gs

js

1function doGet(e) { 2 const t = HtmlService.createTemplateFromFile('index'); 3 return t.evaluate() 4} 5 6function tuikatest() { 7 let spread = SpreadsheetApp.openById("1YV************") 8 let meibo = spread.getSheetByName('名簿'); 9 let lastRow = meibo.getLastRow(); 10 let optionTag = '' 11 // シートから1行ずつ読み取ってoptionタグを構築 12 for (let i = 1; i <= lastRow; i++) { 13 let name = meibo.getRange(i, 1).getValue(); 14 optionTag = optionTag + ('<option value="' + name + '">' + name + '</option>'); 15 } 16 // 構築したタグを返す。 17 return optionTag 18}

index.html

HTML

1<!DOCTYPE html> 2<html> 3 <head> 4 <base target="_top"> 5 </head> 6 <body> 7 生徒名1:<select name="seito1"> <?!= tuikatest() ?> </select> 8 </body> 9</html> 10

質問文のコードから変更したのは下記です。

diff

1 for (let i = 1; i <= lastRow; i++){ 2 let name = meibo.getRange(i,1).getValue(); 3- return('<option value="'+name+'">'+name+'</option>'); 4+ optionTag = optionTag + ('<option value="' + name + '">' + name + '</option>'); 5 } 6+ return optionTag; 7}

元の文ではループが開始してすぐreturnしているので、データが複数行あっても1行分しかデータが返ってきません。
上記のように、ループを回してタグを追加し、最終結果を返すようにしています。

index.html

diff

1- <?= tuikatest() ?> 2+ <?!= tuikatest() ?>

通常の出力スクリプトレットだと、タグがエスケープされHTMLとして解釈されません。
したがって代わりに強制出力スクリプトレットを使用しています。


【非推奨の理由】

上記では、optionタグを構築し、強制出力スクリプトレットを使用してHTMLに表示しています。
しかし、強制出力スクリプトレットをユーザーが自由に設定できる値に使用するのは、セキュリティ上危険です。

今回、ドロップダウンに表示するデータをスプレッドシートから読み取っていますが、スプレッドシートには自由にデータを書き込めます。

optionタグを構築して渡すやり方だと、仮に悪意のある人間がスプレッドシートのデータに細工を施した場合、任意のスクリプトを実行できてしまう可能性があります。
コードインジェクション

たとえば、「名簿」シートの6行目に下記のような記述をすることで、HTML側にアラートを表示することができます。

イメージ説明

強制出力スクリプトレットはタグをエスケープしないため、
html側で

html

1生徒名1:<select name="seito1"> 2 <?= tuikatest() ?> 3 </select>

html

1生徒名1:<select name="seito1"> 2 ... 3 <option value="生徒名6</options><script>alert('test')</script>">生徒名6</options><script>alert('test')</script></option> 4 </select>

と解釈されます。
結果、見た目上は普通のデータがあるだけなのに、裏で<script>タグに記述したスクリプトを実行できてしまうことになります。
(HTML上は、生徒名6の右の引用符が欠けていたり、タグの対応関係がおかしくなっていますが、ブラウザが勝手にエラーを読み飛ばして補完します)

(なお「名簿シートの管理者とcode.gsの作成者は同じであるから実質的な危険はない」等の議論は別の軸の話としてあるかもしれません)


【代替案】
上記ではタグを構築してそのタグの内容をそのまま渡すようになっていたため、scriptタグを記述できてしまう点に問題がありました。

したがって、タグを構築して渡すのではなく、名前リストのデータだけを渡すようにします。

code.gs

js

1function doGet(e) { 2 const t = HtmlService.createTemplateFromFile('index'); 3 return t.evaluate() 4} 5 6function getStudentNames() { 7 let spread = SpreadsheetApp.openById("1Y********************") 8 let meibo = spread.getSheetByName('名簿'); 9 let lastRow = meibo.getLastRow(); 10 // 名前リストを配列として返す 11 let names = meibo.getRange(1, 1, lastRow, 1).getValues(); 12 return names; 13}

 
index.html

html

1<!DOCTYPE html> 2<html> 3 <head> 4 <base target="_top"> 5 </head> 6 <body> 7 <select id="students"></select> 8 9 <script> 10 // GAS側のgetStudentNames()関数を呼び出し、その後onSuccess()を呼び出す。 11 google.script.run.withSuccessHandler(onSuccess).getStudentNames(); 12 13 // namesには、getStudentNames()の戻り値が入る。 14 function onSuccess(names){ 15 let students = document.getElementById("students"); 16 names.forEach(e => students.add(new Option(e, JSON.stringify( { value: e } )))); 17 } 18 </script> 19 20 </body> 21</html>

 
上記コードの場合、データはエスケープして渡されるので、スクリプトが書かれたデータであってもそれが実行されることはありません。
イメージ説明

投稿2021/12/14 15:17

編集2021/12/14 15:46
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

morit

2021/12/16 01:18

詳細な解答ありがとうございます!! パッと読んだだけでは理解できないところも多いため、一度じっくり研究させていただいて、またコメントさせて頂きます!m(_ _)m
morit

2021/12/16 02:06

非推奨の方法だと、スプレッドシート上から、HTMLを改竄できてしまう余地が生まれるということですね。なるほど、そんなリスクがあったとは... for文で複数の項目を作り出したい時は、あらかじめ空の変数(今回ならoptionTag)を定義しておいて、そこに再帰的に要素を加えていく、という方法を取るのですね。これも非常に勉強になりました。 代替案として出していただいた方法は、もう少し理解に時間がかかりそうなので、自分で咀嚼出来次第、またコメントさせていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問