1. はじめに
SBクリエイティブ 「スラスラわかるHTML&CSSのきほん 第3版」のサンプルコードではお問い合わページで入力した内容を表で表示することができる.その仕組みについて理解することを目標にする.
サンプルコードは以下からダウンロードできる.
https://www.sbcr.jp/product/4815611651/
2. 対象ページの挙動
- フォームに各項目を入力し,送信ボタンを押す.
3. この挙動を実現する手順
- フォームに入力する
- ボタンを押す
- 入力内容をURLに埋め込む
- ページを遷移させる
- URLから入力内容を取り出す
- 取り出した入力内容をtableに追加する
3-1. [1~4]フォームに入力からページ遷移
contact.html
で実現する.
contact.html
にはform
タグで囲まれたフォームが定義されている.
<form action="result.html">
<p>
<label for="kind">お問い合わせの種類</label><br>
<select name="kind" id="kind">
<option value="1">ご予約</option>
<option value="2">イベントに関するお問い合わせ</option>
<option value="3">その他</option>
</select>
</p>
<p>
はじめてのご来店ですか?<br>
<label><input type="radio" name="first" value="yes" checked>はい</label><br>
<label><input type="radio" name="first" value="no">いいえ</label>
</p>
<p>
当カフェをお知りになったきっかけは?<br>
<label><input type="checkbox" name="how" value="friend">知り合いの紹介で</label><br>
<label><input type="checkbox" name="how" value="magazine">SNS、Webサイト、雑誌などを見て</label>
</p>
<p>
<label for="subject">お問い合わせの件名</label><br>
<input type="text" name="subject" placeholder="タイトル" id="subject">
</p>
<p>
<label for="message">お問い合わせの具体的な内容</label><br>
<textarea name="message" id="message"></textarea>
</p>
<p class="submit">
<input type="submit" value="送信">
</p>
</form>
form
タグの中ではinput
を以下のように定義したとき,そこで取得した情報はURLに次のように埋め込まれる.
<input name="名前" value="値" />
<url部分>?名前=値
例
http://127.0.0.1:5500/result.html?kind=2&first=yes
form
タグの中ではinput
を複数定義できる.
<input name="名前1" value="値1" />
<input name="名前2" value="値2" />
<input name="名前3" value="値3" />
<url部分>?名前1=値1&名前2=値2&名前3=値3
form
タグ内で定義したinput
の値は,type="submit"
としたinput
でURLに反映させることができる.
<input type="submit" value="送信">
送信ボタンを押した後のページ遷移先は,form
タグ中でaction
を指定する.ドキュメントには以下のように書かれている.
フォーム経由で送信された情報を処理するプログラムの URL(https://developer.mozilla.org/ja/docs/Web/HTML/Element/form)
<form action="result.html">
3-2. [5] URLから入力内容を取り出す
result.html
中で定義されている以下のスクリプトを使う.
<script>
'use strict';
const render = function(q) {
const parent = document.getElementById('result');
for (const item of q) {
parent.insertAdjacentHTML('beforeend', `<tr><td>${item[0]}</td><td>${item[1]}</td></tr>`);
}
}
window.addEventListener('DOMContentLoaded', (e) => {
try {
const urlParam = decodeURIComponent(location.search.replace(/\+/g, ' ')).slice(1);
const qList = urlParam.split('&');
const queries = [];
for (const item of qList) {
const split = item.split('=');
queries.push([split[0], split[1].split('<').join('<').split('>').join('>')]);
}
render(queries);
} catch(err) {
console.log(err);
}
});
</script>
3-2-1. スクリプトの概要
このスクリプトは大きく以下の3つに分けられる.
'use strict';
const render = function(q) {…
window.addEventListener('DOMContentLoaded', (e) => {…
実行順は以下の通り.
'use strict';
window.addEventListener('DOMContentLoaded', (e) => {…
const render = function(q) {…
3-2-2. 各命令・関数について
3-2-2-1. use strict
JavaScriptのコードが厳格モード(Strict mode)で実行されるようになる.今回はあまり気にしなくて良い.
3-2-2-2. window.addEventListener('DOMContentLoaded', (e) => {…
3-2-2-2-1. 関数の概要
window.addEventListener
はターゲットに特定のイベントが配信されるたびに呼び出される関数を設定する.以下のようにして使う.
addEventListener(type, listener)
- type: 対象とするイベントの種類を表す文字列
- listener: typeで指定したイベントが発生するときに通知を受け取るオブジェクト(handleEvent()メソッドを持つオブジェクトまたは関数)
今回はtype
に'DOMContentLoaded'を指定している.
DOMContentLoaded イベントは、HTML の文書が完全に読み込まれ構文解析され、すべての遅延スクリプト(
<script defer src="…">
および<script type="module">
)がダウンロードされ、実行されたときに発生します。画像、サブフレーム、非同期スクリプトの読み込みの完了は待ちません。
(https://developer.mozilla.org/ja/docs/Web/API/Document/DOMContentLoaded_event)
また,listener
には以下の関数を指定している.
(e) => {
try {
const urlParam = decodeURIComponent(location.search.replace(/\+/g, ' ')).slice(1);
const qList = urlParam.split('&');
const queries = [];
for (const item of qList) {
const split = item.split('=');
queries.push([split[0], split[1].split('<').join('<').split('>').join('>')]);
}
render(queries);
} catch(err) {
console.log(err);
}
}
3-2-2-2-2. コールバック関数の詳細
-
引数
- 引数としてeを受け取る.これはイベントオブジェクトであり,今回は使用しない.
-
try-catch
- 関数の全体を構成する.最初に
try
ブロック内のコードが実行され,そこで例外が発生するとcatch
ブロック内のコードが実行される.
- 関数の全体を構成する.最初に
-
const urlParam = decodeURIComponent(location.search.replace(/\+/g, ' ')).slice(1);
- URLのクエリパラメータ文字列内の
+
を?
削除する. - 次の4文に分割できる.
const tmp1 = location.search; const tmp2 = tmp1.replace(/\+/g, ' '); const tmp3 = decodeURIComponent(tmp2); const urlParam = tmp3.slice(1);
-
tmp1
-
location.search
ではクエリ文字列を取得する.クエリ文字列とはURLの?以降の部分.この命令で取得できる文字列には?も含まれる.
-
-
tmp2
-
tmp1
内の+
を- URLでは
+
にエンコードされる.そのため,+
をもとに戻す必要がある.なお,+
は%2B
にエンコードされており,decodeURIComponent
で+
にデコードされる.
- URLでは
-
replace(/\+/g, ' ')
で文字列を置換する.replace
関数は,通常最初に一致した第1引数のパターン(今回は文字列)を第2引数の文字列に置換した文字列を返す. - 第1引数
/\+/g
は正規表現./
,\+
,/g
に分けられる./
は正規表現の始まりを表す.\+
は+
をエスケープした文字列を表す.\g
は通常最初に一致した第1引数のパターンを置換するところ,一致した第1引数のパターン全てを置換するように指定する.
-
-
tmp3
-
decodeURIComponent
関数でエンコードされたURIをデコードする.
-
-
urlParam
- 文字列戦闘の
?
を取り除く -
slice
関数で文字列インデックスが1の文字から先を取得する.つまり2文字目以降を取得する.
- 文字列戦闘の
分割したときの各変数の値は,例えば以下のようになる.
tmp1 = ?kind=1&first=yes&subject=%E4%BB%B6%E5%90%8D&message=%E5%86%85%E5%AE%B9+%E3%81%A7%E3%81%99 tmp2 = ?kind=1&first=yes&subject=%E4%BB%B6%E5%90%8D&message=%E5%86%85%E5%AE%B9 %E3%81%A7%E3%81%99 tmp3 = ?kind=1&first=yes&subject=件名&message=内容 です urlParam = kind=1&first=yes&subject=件名&message=内容 です
- URLのクエリパラメータ文字列内の
-
const qList = urlParam.split('&');
-
urlParam
の文字列を&
で分割して配列にする.配列には&
は含まれない. - 例えば以下のように処理される.
urlParam = kind=1&first=yes&subject=件名&message=内容 です qList= [ "kind=1", "first=yes", "subject=件名", "message=内容 です" ]
-
-
const queries = [];
- 空の配列を作る.
-
for (const item of qList) {…
-
queries
を2次元配列として,i番目のクエリについて[i][0]
成分に左辺を,[i][1]
成分には右辺を格納する. -
範囲for文で処理する.
-
queries.push([split[0], split[1].split('<').join('<').split('>').join('>')]);
-
splitの
<
を<
に,>
を>
に置換する.この文は以下の6文に分割できる.const tmpList1 = split[1].split('<'); const tmpStr2 = tmpList1.join('<'); const tmpList3 = tmpStr2.split('>'); const tmpStr4 = tmpList3.join('>'); const tmpList5 = [split[0], tmpStr4]; queries.push(tmpList5);
split
で分割された配列はjoin
関数により引数で指定した文字列で結合される.例えばitem
が"message=a<b>c"
であるときは以下のように処理される.item = "message=a<b>c" split = [ "message", "a<b>c" ] tmpList1 = [ "a", "b>c" ] tmpStr2 = a<b>c tmpList3 = [ "a<b", "c" ] tmpStr4 = a<b>c tmpList5 = [ "message", "a<b>c" ]
<
と>
を<
と>
に置換するのは,クロスサイトスクリプティング(XSS)攻撃を防止するためだ.ここで処理する文字列は後でDOM操作によりhtmlコードとして挿入される.このときに<>
が残っていたとき,これがタグとして解釈される場合がある.攻撃の意思が無いとしても,誤動作の原因になるため,それを防止する.
このfor文で
queries
は以下のようになる.qList = [ "kind=1", "first=yes", "subject=件名", "message=内容 です" ] queries = [ [ "kind", "1" ], [ "first", "yes" ], [ "subject", "件名" ], [ "message", "内容 です" ] ]
-
-
3-2-2-3. const render = function(q) {…
関数render
は以下の通り.
const render = function(q) {
const parent = document.getElementById('result');
for (const item of q) {
parent.insertAdjacentHTML('beforeend', `<tr><td>${item[0]}</td><td>${item[1]}</td></tr>`);
}
}
-
const parent = document.getElementById('result');
- idがresultである要素を取得する
-
for (const item of q) {…
-
parent
に対してq
の内容を表の行として追加するfor (const item of q) { parent.insertAdjacentHTML('beforeend', `<tr><td>${item[0]}</td><td>${item[1]}</td></tr>`); }
-
parent.insertAdjacentHTML('beforeend',
<tr><td>${item[0]}</td><td>${item[1]}</td></tr>
);-
insertAdjacentHTML
関数で1列目がitem[0]
,2列目がitem[1]
表の行を追加する -
insertAdjacentHTML
関数の使い方は以下の通りinsertAdjacentHTML(position, text)
- position:要素の相対的な位置を表す文字列
- text:HTMLまたはXMLとして解釈して挿入される文字列
- 今回は
'beforeend'
により要素のすぐ内側の最後の子として挿入される - 挿入内容は以下のとおり
<tr> <td>${item[0]}</td> <td>${item[1]}</td> </tr>
-
-
-
4. おわりに
kujiraじゃなくてkuziraであってるらしい(超余談).