いろいろ作っていて四字熟語マスタが必要になってそこから1つ正解を選択することになった。
↓の都道府県みたいに47個だけならまだドロップダウンリスト(<select>)でOKかなと思う。
都道府県:でも四字熟語ってもっと数が多いし、難しい漢字から探すのは大変すぎる。
そこから探せって言われてもなかなかの苦行なので、他に方法はないか探してみました。
select multiple
<select>タグ内にmultipleを付けるだけ。
<span>都道府県:</span>
<select multiple style="border-width: 1px;">
<option>北海道</option>
<option>青森県</option>
<option>秋田県</option>
<option>岩手県</option>
<option>山形県</option>
<option>宮城県</option>
<option>福島県</option>
<option>茨城県</option>
<option>栃木県</option>
<option>群馬県</option>
<option>埼玉県</option>
<option>千葉県</option>
<option>東京都</option>
<option>神奈川県</option>
<option>山梨県</option>
<option>長野県</option>
<option>新潟県</option>
<option>富山県</option>
<option>石川県</option>
<option>福井県</option>
<option>静岡県</option>
<option>愛知県</option>
<option>岐阜県</option>
<option>三重県</option>
<option>滋賀県</option>
<option>京都府</option>
<option>大阪府</option>
<option>奈良県</option>
<option>和歌山県</option>
<option>兵庫県</option>
<option>鳥取県</option>
<option>島根県</option>
<option>岡山県</option>
<option>広島県</option>
<option>山口県</option>
<option>香川県</option>
<option>愛媛県</option>
<option>徳島県</option>
<option>高知県</option>
<option>福岡県</option>
<option>大分県</option>
<option>熊本県</option>
<option>佐賀県</option>
<option>長崎県</option>
<option>宮崎県</option>
<option>鹿児島県</option>
<option>沖縄県</option>
</select>リストボックス形式になってくれてこれは良い!
と思ったけれど、multiple=複数の名のとおり、複数選択できてしまいます。
どうしても単一選択でないとダメだからこれは使えない。
気を付けて1つ選べば良い話かもしれないが、間違えて2つ以上選択するリスクもなくはないので、他の方法を探すことに。
datalist
HTMLには<datalist>タグというとても便利なものがあります。
<span>都道府県:</span>
<input type="text" name="todo_id" list="todo_list" style="border-width: 1px;">
<datalist id="todo_list">
<option>北海道</option>
<option>青森県</option>
<option>秋田県</option>
<option>岩手県</option>
<option>山形県</option>
<option>宮城県</option>
<option>福島県</option>
<option>茨城県</option>
<option>栃木県</option>
<option>群馬県</option>
<option>埼玉県</option>
<option>千葉県</option>
<option>東京都</option>
<option>神奈川県</option>
<option>山梨県</option>
<option>長野県</option>
<option>新潟県</option>
<option>富山県</option>
<option>石川県</option>
<option>福井県</option>
<option>静岡県</option>
<option>愛知県</option>
<option>岐阜県</option>
<option>三重県</option>
<option>滋賀県</option>
<option>京都府</option>
<option>大阪府</option>
<option>奈良県</option>
<option>和歌山県</option>
<option>兵庫県</option>
<option>鳥取県</option>
<option>島根県</option>
<option>岡山県</option>
<option>広島県</option>
<option>山口県</option>
<option>香川県</option>
<option>愛媛県</option>
<option>徳島県</option>
<option>高知県</option>
<option>福岡県</option>
<option>大分県</option>
<option>熊本県</option>
<option>佐賀県</option>
<option>長崎県</option>
<option>宮崎県</option>
<option>鹿児島県</option>
<option>沖縄県</option>
</datalist>上記のように<input>タグと組み合わせて使います。
<datalist>タグは見えないので、見た目はただの<input>タグでしかないですが、
例えば、上記のテキストボックスに「福」と入力すると、
「福島県」「福井県」「福岡県」と入力候補が出てきてくれます。
これは便利ですよね!
でも、この方法だと取得できるのはname属性でなく文字列(上記の例でいう都道府県)なのでこの方法も使えない。
うーん、惜しい。
実はできなくもないのですが、
<span>都道府県:</span>
<input type="text" name="todo_id" list="todo_list" style="border-width: 1px;">
<datalist id="todo_list">
<option value="hokkaido">北海道</option>
<option value="aomori">青森県</option>
<option value="akita">秋田県</option>
<option value="iwate">岩手県</option>
<option value="yamagata">山形県</option>
<option value="miyagi">宮城県</option>
<option value="fukushima">福島県</option>
<option value="ibaraki">茨城県</option>
<option value="tochigi">栃木県</option>
<option value="gunma">群馬県</option>
<option value="saitama">埼玉県</option>
<option value="chiba">千葉県</option>
<option value="tokyo">東京都</option>
<option value="kanagawa">神奈川県</option>
<option value="yamanashi">山梨県</option>
<option value="nagano">長野県</option>
<option value="niigata">新潟県</option>
<option value="toyama">富山県</option>
<option value="ishikawa">石川県</option>
<option value="fukui">福井県</option>
<option value="shizuoka">静岡県</option>
<option value="aichi">愛知県</option>
<option value="gifu">岐阜県</option>
<option value="mie">三重県</option>
<option value="shiga">滋賀県</option>
<option value="kyoto">京都府</option>
<option value="osaka">大阪府</option>
<option value="nara">奈良県</option>
<option value="wakayama">和歌山県</option>
<option value="hyogo">兵庫県</option>
<option value="tottori">鳥取県</option>
<option value="shimane">島根県</option>
<option value="okayama">岡山県</option>
<option value="hiroshima">広島県</option>
<option value="yamaguchi">山口県</option>
<option value="kagawa">香川県</option>
<option value="ehime">愛媛県</option>
<option value="tokushima">徳島県</option>
<option value="kochi">高知県</option>
<option value="fukuoka">福岡県</option>
<option value="oita">大分県</option>
<option value="kumamoto">熊本県</option>
<option value="saga">佐賀県</option>
<option value="nagasaki">長崎県</option>
<option value="miyazaki">宮崎県</option>
<option value="kagoshima">鹿児島県</option>
<option value="okinawa">沖縄県</option>
</datalist>とvalueがメインで表示されてしまいます。
出来れば逆にしたいですね。。。
それに致命的なのは、<datalist>+<input>タグの組み合わせだと、あくまでも<input>タグに入力されたものが送信されるので、
POSTやGETされてきた入力値は、<datalist>から選択されたものの保証は無いんです。
<input>タグって自由に入力できてしまいますよね?
<datalist>には<input>タグの入力規制機能はありません。
なのでこれにちょっと手を加えたいと思います。
datalistを使った安全なidの送信方法
実は何でも入力できてしまうdatalist。
そんなdatalistを使った安全なidの送信方法をお伝えします。
テキストボックス+JavaScriptで送信する
まず、datalistに入力させたい文字列(例では都道府県)をvalueに入力し<option>と</option>の間は空にします。
それと同時に、datalistの検索用のテキストボックスとは別にidを送るためのhiddenにしたテキストボックスを設置します。
<label>都道府県:</label>
<input type="text" id="todo_search" list="todo_list">
<input type="hidden" name="todo_id" id="todo_id">
<datalist id="todo_list">
<option value="北海道"></option>
<option value="青森県"></option>
<option value="秋田県"></option>
<option value="岩手県"></option>
<option value="山形県"></option>
<option value="宮城県"></option>
<option value="福島県"></option>
<option value="茨城県"></option>
<option value="栃木県"></option>
<option value="群馬県"></option>
<option value="埼玉県"></option>
<option value="千葉県"></option>
<option value="東京都"></option>
<option value="神奈川県"></option>
<option value="山梨県"></option>
<option value="長野県"></option>
<option value="新潟県"></option>
<option value="富山県"></option>
<option value="石川県"></option>
<option value="福井県"></option>
<option value="静岡県"></option>
<option value="愛知県"></option>
<option value="岐阜県"></option>
<option value="三重県"></option>
<option value="滋賀県"></option>
<option value="京都府"></option>
<option value="大阪府"></option>
<option value="奈良県"></option>
<option value="和歌山県"></option>
<option value="兵庫県"></option>
<option value="鳥取県"></option>
<option value="島根県"></option>
<option value="岡山県"></option>
<option value="広島県"></option>
<option value="山口県"></option>
<option value="香川県"></option>
<option value="愛媛県"></option>
<option value="徳島県"></option>
<option value="高知県"></option>
<option value="福岡県"></option>
<option value="大分県"></option>
<option value="熊本県"></option>
<option value="佐賀県"></option>
<option value="長崎県"></option>
<option value="宮崎県"></option>
<option value="鹿児島県"></option>
<option value="沖縄県"></option>
</datalist>また、JavaScript内にid表を作り、name(入力させたい文字列)と同じ文字が入力された場合だけ(完全一致チェック)
idをhiddenにしたテキストのvalueに自動入力します。
つまり、入力された文字は送られることはなく、サーバーに送られるのはidだけになります。
document.addEventListener("DOMContentLoaded", function(e) {
const todofuken = [
{id: 'hokkaido', name: "北海道"},
{id: 'aomori', name: "青森県"},
{id: 'akita', name: "秋田県"},
{id: 'iwate', name: "岩手県"},
{id: 'yamagata', name: "山形県"},
{id: 'miyagi', name: "宮城県"},
{id: 'fukushima', name: "福島県"},
{id: 'ibaraki', name: "茨城県"},
{id: 'tochigi', name: "栃木県"},
{id: 'gunma', name: "群馬県"},
{id: 'saitama', name: "埼玉県"},
{id: 'chiba', name: "千葉県"},
{id: 'tokyo', name: "東京都"},
{id: 'kanagawa', name: "神奈川県"},
{id: 'yamanashi', name: "山梨県"},
{id: 'nagano', name: "長野県"},
{id: 'niigata', name: "新潟県"},
{id: 'toyoma', name: "富山県"},
{id: 'ishikawa', name: "石川県"},
{id: 'fukui', name: "福井県"},
{id: 'shizuoka', name: "静岡県"},
{id: 'aichi', name: "愛知県"},
{id: 'gifu', name: "岐阜県"},
{id: 'mie', name: "三重県"},
{id: 'shiga', name: "滋賀県"},
{id: 'kyoto', name: "京都府"},
{id: 'osaka', name: "大阪府"},
{id: 'nara', name: "奈良県"},
{id: 'wakayama', name: "和歌山県"},
{id: 'hyogo', name: "兵庫県"},
{id: 'tottori', name: "鳥取県"},
{id: 'shimane', name: "島根県"},
{id: 'okayama', name: "岡山県"},
{id: 'hiroshima', name: "広島県"},
{id: 'yamaguchi', name: "山口県"},
{id: 'kagawa', name: "香川県"},
{id: 'ehime', name: "愛媛県"},
{id: 'tokushima', name: "徳島県"},
{id: 'kochi', name: "高知県"},
{id: 'fukuoka', name: "福岡県"},
{id: 'oita', name: "大分県"},
{id: 'kumamoto', name: "熊本県"},
{id: 'saga', name: "佐賀県"},
{id: 'nagasaki', name: "長崎県"},
{id: 'miyazaki', name: "宮崎県"},
{id: 'kagoshima', name: "鹿児島県"},
{id: 'okinawa', name: "沖縄県"}
];
const todoSearch = document.getElementById("todo_search");
const todoId = document.getElementById("todo_id");
if (!todoSearch || !todoId) return;
todoSearch.addEventListener("input", function() {
const match = todofuken.find(t => t.name === this.value);
if (match) {
todoId.value = match.id;
} else {
todoId.value = ""; // 不正入力防止
}
})
});さらに堅牢にするならJavaScriptのsubmitで規制をかけます。
<form id="todo_form" method="post">
...
<button type="submit">送信</button>
</form>const form = document.getElementById("todo_form");
form.addEventListener("submit", function(e) {
if (!todoId.value) {
e.preventDefault();
alert("正しい都道府県を選択してください");
}
});上記を合わせるとこんな感じになります。
See the Pen safe datalist by gulf_stream (@Hitomi-Kuge) on CodePen.
サーバー側でホワイトリストを作って規制する
念のため、送信先のPHP側でもホワイトリストを作ってバリデーションをしておきます。
$allowed = [
'hokkaido','aomori','akita','iwate','yamagata','miyagi','fukushima',
'ibaraki','tochigi','gunma','saitama','chiba','tokyo','kanagawa',
'yamanashi','nagano','niigata','toyama','ishikawa','fukui',
'shizuoka','aichi','gifu','mie','shiga','kyoto','osaka','nara',
'wakayama','hyogo','tottori','shimane','okayama','hiroshima',
'yamaguchi','kagawa','ehime','tokushima','kochi','fukuoka',
'oita','kumamoto','saga','nagasaki','miyazaki','kagoshima','okinawa'
];
$todo_id = $_POST['todo_id'] ?? '';
if (!in_array($todo_id, $allowed, true)) {
die('不正な入力です');
}これでdatalistからidを安全に送信することが出来ます。
おわりに
今回は、如何に膨大なリストから1つだけ対象の項目を選択するUIを作るかについてご紹介しました。
実はdatalistは初めて知ったのですが、何でも入力できてしまう落とし穴があったんですね。
今回の記事が少しでも参考になれば嬉しいです!

