htmx 逆引きレシピ
オートコンプリートを作るには?
入力しながら候補が出るオートコンプリートは、入力の手間を減らし、表記ゆれや選択ミスも防げます。
htmxなら、候補リストだけをサーバから取り直して差し替えるだけで実現でき、JSに依存しないUIが作れます。
このページでは「取引先」「タグ」「部署」に加え、実務で便利な「コード+名称表示&コード保持」までまとめて紹介します。
使用するhtmx属性
hx-get:入力のたびにGETで候補HTMLを取得し、候補リストを更新します(オートコンプリート向きです)。hx-trigger:リクエストの発火タイミングを指定します(例:keyup changed delay:250ms/search/submit)。hx-target:返ってきたHTMLを差し替える先(候補リストや結果エリア)を指定します。hx-swap:差し替え方を指定します(候補はinnerHTML、タグ一覧はouterHTMLが扱いやすいです)。hx-swap-oob:クリック確定時に、入力欄・候補欄・hidden値などを同時更新し、画面の整合性を保ちます。
利用シーン
- 「取引先名の候補」:入力途中でも候補を絞り込み、表記ゆれや選択ミスを減らしたいときに便利です。
- 「タグ入力」:候補を見ながらタグを追加・削除し、チップ表示だけを部分更新で管理したいときに向きます。
- 「部署名の候補」:固定候補が多い入力で、部分一致検索+クリック確定の入力補助を簡単に用意したいときに使えます。
- 「コード+名称表示&コード保持」:同名が多いマスタ入力で、表示は名称、保存はコードにしたい“業務UI”に最適です。
共通PHP
PHP(共通/_ac_data.php)
<?php
// 文字列をHTMLエスケープする関数を用意する
function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); }
// 取引先データ(デモ用)を用意する
$AC_VENDORS = ["株式会社みかん商事","みかんフーズ合同会社","Tanaka Trading","Sato Supplies","Suzuki Systems","Orange Works Inc.","MochiMochi Lab","ミカン企画","北関東みかん物流","西日本オレンジ販売"];
// 部署データ(デモ用)を用意する
$AC_DEPTS = ["総務部","人事部","経理部","営業部","営業企画部","カスタマーサポート部","開発部","SRE部","情報システム部","法務部"];
// タグ候補(デモ用)を用意する
$AC_TAGS = ["urgent","important","expense","travel","meeting","client","contract","approval","draft","done","blocked","security","ops","frontend","backend","infra","review"];
// 部分一致で候補を絞る関数を用意する
function ac_filter(array $list, string $q, int $limit = 8): array {
// 検索語を小文字にして比較しやすくする
$needle = mb_strtolower($q);
// 結果配列を用意する
$out = [];
// 配列を先頭から走査する
foreach ($list as $v) {
// 比較用に小文字へ変換する
$hay = mb_strtolower((string)$v);
// 部分一致なら候補に追加する
if ($needle !== '' && mb_strpos($hay, $needle) !== false) { $out[] = (string)$v; }
// 上限に達したら打ち切る
if (count($out) >= $limit) { break; }
}
// 結果を返す
return $out;
}
// タグ表示(チップ)HTMLを組み立てる関数を用意する
function ac_render_tags(array $tags): string {
// 出力文字列を用意する
$html = '';
// 外枠(この要素をouterHTMLで差し替える)
$html .= '<div id="DEMO_AC_TAGS_TAGS" class="CARD">';
// タグ行を開始する
$html .= '<div class="TAG_ROW">';
// タグが空ならプレースホルダを出す
if (count($tags) === 0) {
// まだタグがない表示を出す
$html .= '<span class="HTMX-NOTE">(まだタグはありません)</span>';
} else {
// タグを順に描画する
foreach ($tags as $t) {
// 1タグ分のチップを出す
$html .= '<span class="TAG">';
// タグ名を出す
$html .= '<span>' . h((string)$t) . '</span>';
// 削除ボタン(GETで削除)
$html .= '<button type="button" aria-label="remove" hx-get="/htmx/demo/_ac_tags_remove.php?tag=' . rawurlencode((string)$t) . '" hx-target="#DEMO_AC_TAGS_TAGS" hx-swap="outerHTML">×</button>';
// チップを閉じる
$html .= '</span>';
}
}
// タグ行を閉じる
$html .= '</div>';
// hidden(本来の送信用の想定)を出す
$html .= '<input type="hidden" name="tags_csv" value="' . h(implode(',', $tags)) . '">';
// 外枠を閉じる
$html .= '</div>';
// 組み立てたHTMLを返す
return $html;
}
① 取引先名の候補(ライブ候補)
入力に応じて候補をサーバで絞り込み、候補リストだけを差し替えます。
候補をクリックすると入力欄に反映し、候補リストもクリアします。
HTML
<div class="DEMO">
<h4>① 取引先名の候補(ライブ候補)</h4>
<form class="FORM" onsubmit="return false;">
<label>
取引先名
<input
id="DEMO_AC_VENDOR_INPUT"
type="text"
name="q"
placeholder="例:みかん / tanaka / 株式会社…"
autocomplete="off"
hx-get="/htmx/demo/_ac_vendor_suggest.php"
hx-trigger="keyup changed delay:250ms, search"
hx-target="#DEMO_AC_VENDOR_SUGGEST"
hx-swap="innerHTML"
>
</label>
</form>
<div id="DEMO_AC_VENDOR_SUGGEST" class="AC_SUGGEST">
<p class="HTMX-NOTE">(ここに候補が表示されます)</p>
</div>
<div id="DEMO_AC_VENDOR_RESULT" class="CARD RESULT">
<p class="HTMX-NOTE">(ここに選択結果が表示されます)</p>
</div>
</div>
PHP(候補表示/_ac_vendor_suggest.php)
<?php
// セッションを開始する(デモでは未使用だが統一のため)
session_start();
// 共通データと関数を読み込む
require __DIR__ . '/_ac_data.php';
// 検索語を受け取る
$q = (string)($_GET['q'] ?? '');
// 前後の空白を除去する
$q = trim($q);
// 空なら案内だけ返す
if ($q === '') {
// 候補欄の案内を出す
echo '<p class="HTMX-NOTE">(入力すると候補が表示されます)</p>';
// ここで終了する
exit;
}
// 候補を絞り込む
$list = ac_filter($AC_VENDORS, $q, 8);
// 1件もなければ案内を返す
if (count($list) === 0) {
// 見つからない表示を出す
echo '<p class="HTMX-NOTE">候補が見つかりませんでした。</p>';
// ここで終了する
exit;
}
// リストの開始タグを出す
echo '<ul class="AC_LIST">';
// 候補を1件ずつ描画する
foreach ($list as $name) {
// 1行分を開始する
echo '<li class="AC_ITEM">';
// クリックで確定(入力欄もOOBで更新)
echo '<button type="button" class="BTN is-sub" hx-get="/htmx/demo/_ac_vendor_pick.php?name=' . rawurlencode($name) . '" hx-target="#DEMO_AC_VENDOR_RESULT" hx-swap="innerHTML">' . h($name) . '</button>';
// 1行分を閉じる
echo '</li>';
}
// リストの終了タグを出す
echo '</ul>';
PHP(確定/_ac_vendor_pick.php)
<?php
// セッションを開始する(デモでは未使用だが統一のため)
session_start();
// 共通データと関数を読み込む
require __DIR__ . '/_ac_data.php';
// 選択された取引先名を受け取る
$name = (string)($_GET['name'] ?? '');
// 前後の空白を除去する
$name = trim($name);
// 空ならエラーを返す
if ($name === '') {
// エラー表示を返す
echo '<p class="HTMX-NOTE">選択に失敗しました。</p>';
// ここで終了する
exit;
}
// 選択結果を返す(メインレスポンス)
echo '<p><strong>選択:</strong>' . h($name) . '</p>';
// 入力欄をOOBで差し替えて値を反映する
echo '<input id="DEMO_AC_VENDOR_INPUT" type="text" name="q" value="' . h($name) . '" placeholder="例:みかん / tanaka / 株式会社…" autocomplete="off" hx-get="/htmx/demo/_ac_vendor_suggest.php" hx-trigger="keyup changed delay:250ms, search" hx-target="#DEMO_AC_VENDOR_SUGGEST" hx-swap="innerHTML" hx-swap-oob="outerHTML">';
// 候補欄をOOBでクリアする
echo '<div id="DEMO_AC_VENDOR_SUGGEST" hx-swap-oob="innerHTML"></div>';
デモ
① 取引先名の候補(ライブ候補)
(ここに候補が表示されます)
(ここに選択結果が表示されます)
解説
- 入力欄に
hx-getを付け、入力のたびに候補取得URLへリクエストします。 hx-trigger="keyup changed delay:250ms, search"により、タイプ中の連打を抑えつつ、検索UIとして自然なタイミングで発火します。- 候補ボタンをクリックすると「確定用URL」に飛び、結果エリアに選択結果を表示します。
- 確定レスポンスでは
hx-swap-oobを使い、入力欄の値を選択名に差し替え、候補リストも同時にクリアしています。
② タグ入力(候補+追加/削除)
入力でタグ候補を出し、クリックや送信でタグを追加します。
タグの状態はデモ用にセッションで保持し、表示だけ部分更新します。
HTML
<div class="DEMO">
<h4>② タグ入力(候補+追加/削除)</h4>
<p class="HTMX-NOTE">
入力でタグ候補を出し、クリックや送信でタグを追加します。<br>
タグの状態はデモ用にセッションで保持し、表示だけ部分更新します。
</p>
<form
id="DEMO_AC_TAGS_FORM"
class="FORM"
method="get"
hx-get="/htmx/demo/_ac_tags_add.php"
hx-trigger="submit"
hx-target="#DEMO_AC_TAGS_TAGS"
hx-swap="outerHTML"
>
<label>
タグ(入力→候補クリック / 追加ボタン)
<input
id="DEMO_AC_TAGS_INPUT"
type="text"
name="tag_new"
placeholder="例:urgent / travel / expense…"
autocomplete="off"
hx-get="/htmx/demo/_ac_tags_suggest.php"
hx-trigger="keyup changed delay:250ms, search"
hx-target="#DEMO_AC_TAGS_SUGGEST"
hx-swap="innerHTML"
>
</label>
<button class="BTN is-ok" type="submit">+ 追加</button>
</form>
<div id="DEMO_AC_TAGS_SUGGEST" class="AC_SUGGEST">
<p class="HTMX-NOTE">(ここに候補が表示されます)</p>
</div>
<!-- タグ一覧(ここを差し替える) -->
<div id="DEMO_AC_TAGS_TAGS" class="CARD">
<div class="TAG_ROW">
<span class="HTMX-NOTE">(まだタグはありません)</span>
</div>
</div>
</div>
PHP(候補表示/_ac_tags_suggest.php)
<?php
// セッションを開始する
session_start();
// 共通データと関数を読み込む
require __DIR__ . '/_ac_data.php';
// 検索語を受け取る
$q = (string)($_GET['tag_new'] ?? $_GET['q'] ?? '');
// 前後の空白を除去する
$q = trim($q);
// 既に選択済みのタグを取得する
$selected = (array)($_SESSION['DEMO_AC_TAGS'] ?? []);
// 空なら案内を返す
if ($q === '') {
// 候補欄の案内を出す
echo '<p class="HTMX-NOTE">(入力するとタグ候補が表示されます)</p>';
// ここで終了する
exit;
}
// 候補を絞り込む
$list = ac_filter($AC_TAGS, $q, 10);
// 選択済みを除外する
$list = array_values(array_filter($list, function ($t) use ($selected) { return !in_array($t, $selected, true); }));
// 1件もなければ案内を返す
if (count($list) === 0) {
// 見つからない表示を出す
echo '<p class="HTMX-NOTE">候補が見つかりませんでした。</p>';
// ここで終了する
exit;
}
// リストの開始タグを出す
echo '<ul class="AC_LIST">';
// 候補を1件ずつ描画する
foreach ($list as $tag) {
// 1行分を開始する
echo '<li class="AC_ITEM">';
// クリックで追加(タグ一覧をouterHTMLで更新)
echo '<button type="button" class="BTN is-sub" hx-get="/htmx/demo/_ac_tags_add.php?tag=' . rawurlencode($tag) . '" hx-target="#DEMO_AC_TAGS_TAGS" hx-swap="outerHTML">' . h($tag) . '</button>';
// 1行分を閉じる
echo '</li>';
}
// リストの終了タグを出す
echo '</ul>';
PHP(追加/_ac_tags_add.php)
<?php
// セッションを開始する
session_start();
// 共通データと関数を読み込む
require __DIR__ . '/_ac_data.php';
// セッションの配列を初期化する
if (!isset($_SESSION['DEMO_AC_TAGS']) || !is_array($_SESSION['DEMO_AC_TAGS'])) { $_SESSION['DEMO_AC_TAGS'] = []; }
// クリック追加用のtagを受け取る
$tag = (string)($_GET['tag'] ?? '');
// フォーム送信用のtag_newを受け取る
$tagNew = (string)($_GET['tag_new'] ?? '');
// 優先して使うタグ文字列を決める
$raw = ($tag !== '') ? $tag : $tagNew;
// 前後の空白を除去する
$raw = trim($raw);
// 空なら現状のタグ表示だけ返す
if ($raw === '') {
// 現在のタグ配列を取得する
$tags = (array)$_SESSION['DEMO_AC_TAGS'];
// タグ表示を返す
echo ac_render_tags($tags);
// ここで終了する
exit;
}
// すでに存在するなら追加しない
if (!in_array($raw, $_SESSION['DEMO_AC_TAGS'], true)) { $_SESSION['DEMO_AC_TAGS'][] = $raw; }
// 現在のタグ配列を取得する
$tags = (array)$_SESSION['DEMO_AC_TAGS'];
// タグ表示(ターゲット差し替え用)を返す
echo ac_render_tags($tags);
// 入力欄をOOBでクリアする(入力を空に戻す)
echo '<input id="DEMO_AC_TAGS_INPUT" type="text" name="tag_new" value="" placeholder="例:urgent / travel / expense…" autocomplete="off" hx-get="/htmx/demo/_ac_tags_suggest.php" hx-trigger="keyup changed delay:250ms, search" hx-target="#DEMO_AC_TAGS_SUGGEST" hx-swap="innerHTML" hx-swap-oob="outerHTML">';
// 候補欄をOOBでクリアする
echo '<div id="DEMO_AC_TAGS_SUGGEST" hx-swap-oob="innerHTML"></div>';
PHP(削除/_ac_tags_remove.php)
<?php
// セッションを開始する
session_start();
// 共通データと関数を読み込む
require __DIR__ . '/_ac_data.php';
// セッションの配列を初期化する
if (!isset($_SESSION['DEMO_AC_TAGS']) || !is_array($_SESSION['DEMO_AC_TAGS'])) { $_SESSION['DEMO_AC_TAGS'] = []; }
// 削除対象のタグを受け取る
$tag = (string)($_GET['tag'] ?? '');
// 前後の空白を除去する
$tag = trim($tag);
// 削除処理(一致しないものだけ残す)
$_SESSION['DEMO_AC_TAGS'] = array_values(array_filter($_SESSION['DEMO_AC_TAGS'], function ($t) use ($tag) { return (string)$t !== $tag; }));
// 現在のタグ配列を取得する
$tags = (array)$_SESSION['DEMO_AC_TAGS'];
// タグ表示を返す
echo ac_render_tags($tags);
// 候補欄をOOBでクリアする(削除時も見た目を安定させる)
echo '<div id="DEMO_AC_TAGS_SUGGEST" hx-swap-oob="innerHTML"></div>';
デモ
② タグ入力(候補+追加/削除)
入力でタグ候補を出し、クリックや送信でタグを追加します。
タグの状態はデモ用にセッションで保持し、表示だけ部分更新します。
(ここに候補が表示されます)
解説
- タグ入力欄は候補表示用に
hx-getを持ち、入力値からタグ候補を絞り込みます。 - 候補クリック、または「追加」ボタン(フォームsubmit)で追加処理へ送信し、タグ一覧(チップ)だけを
outerHTMLで更新します。 - デモでは状態をセッションに保持し、サーバ側で「現在のタグ一覧HTML」を組み立てて返しています(JSで配列管理しなくて済みます)。
- 追加後は
hx-swap-oobで入力欄を空に戻し、候補欄もクリアして操作を連続しやすくしています。
③ 部署名の候補(入力で絞り込み)
部署名は候補が固定になりやすいので「部分一致で絞る」形が扱いやすいです。
候補クリックで入力欄に反映し、候補を消します。
HTML
<div class="DEMO">
<h4>③ 部署名の候補(入力で絞り込み)</h4>
<p class="HTMX-NOTE">
部署名は候補が固定になりやすいので「部分一致で絞る」形が扱いやすいです。<br>
候補クリックで入力欄に反映し、候補を消します。
</p>
<form class="FORM" onsubmit="return false;">
<label>
部署名
<input
id="DEMO_AC_DEPT_INPUT"
type="text"
name="q"
placeholder="例:総務 / 開発 / 営業…"
autocomplete="off"
hx-get="/htmx/demo/_ac_dept_suggest.php"
hx-trigger="keyup changed delay:250ms, search"
hx-target="#DEMO_AC_DEPT_SUGGEST"
hx-swap="innerHTML"
>
</label>
</form>
<div id="DEMO_AC_DEPT_SUGGEST" class="AC_SUGGEST">
<p class="HTMX-NOTE">(ここに候補が表示されます)</p>
</div>
<div id="DEMO_AC_DEPT_RESULT" class="CARD RESULT">
<p class="HTMX-NOTE">(ここに選択結果が表示されます)</p>
</div>
</div>
PHP(候補表示/_ac_dept_suggest.php)
<?php
// セッションを開始する(デモでは未使用だが統一のため)
session_start();
// 共通データと関数を読み込む
require __DIR__ . '/_ac_data.php';
// 検索語を受け取る
$q = (string)($_GET['q'] ?? '');
// 前後の空白を除去する
$q = trim($q);
// 空なら案内だけ返す
if ($q === '') {
// 候補欄の案内を出す
echo '<p class="HTMX-NOTE">(入力すると候補が表示されます)</p>';
// ここで終了する
exit;
}
// 候補を絞り込む
$list = ac_filter($AC_DEPTS, $q, 8);
// 1件もなければ案内を返す
if (count($list) === 0) {
// 見つからない表示を出す
echo '<p class="HTMX-NOTE">候補が見つかりませんでした。</p>';
// ここで終了する
exit;
}
// リストの開始タグを出す
echo '<ul class="AC_LIST">';
// 候補を1件ずつ描画する
foreach ($list as $name) {
// 1行分を開始する
echo '<li class="AC_ITEM">';
// クリックで確定(入力欄もOOBで更新)
echo '<button type="button" class="BTN is-sub" hx-get="/htmx/demo/_ac_dept_pick.php?name=' . rawurlencode($name) . '" hx-target="#DEMO_AC_DEPT_RESULT" hx-swap="innerHTML">' . h($name) . '</button>';
// 1行分を閉じる
echo '</li>';
}
// リストの終了タグを出す
echo '</ul>';
PHP(確定/_ac_dept_pick.php)
<?php
// セッションを開始する(デモでは未使用だが統一のため)
session_start();
// 共通データと関数を読み込む
require __DIR__ . '/_ac_data.php';
// 選択された部署名を受け取る
$name = (string)($_GET['name'] ?? '');
// 前後の空白を除去する
$name = trim($name);
// 空ならエラーを返す
if ($name === '') {
// エラー表示を返す
echo '<p class="HTMX-NOTE">選択に失敗しました。</p>';
// ここで終了する
exit;
}
// 選択結果を返す(メインレスポンス)
echo '<p><strong>選択:</strong>' . h($name) . '</p>';
// 入力欄をOOBで差し替えて値を反映する
echo '<input id="DEMO_AC_DEPT_INPUT" type="text" name="q" value="' . h($name) . '" placeholder="例:総務 / 開発 / 営業…" autocomplete="off" hx-get="/htmx/demo/_ac_dept_suggest.php" hx-trigger="keyup changed delay:250ms, search" hx-target="#DEMO_AC_DEPT_SUGGEST" hx-swap="innerHTML" hx-swap-oob="outerHTML">';
// 候補欄をOOBでクリアする
echo '<div id="DEMO_AC_DEPT_SUGGEST" hx-swap-oob="innerHTML"></div>';
デモ
③ 部署名の候補(入力で絞り込み)
部署名は候補が固定になりやすいので「部分一致で絞る」形が扱いやすいです。
候補クリックで入力欄に反映し、候補を消します。
(ここに候補が表示されます)
(ここに選択結果が表示されます)
解説
- 部署名は候補が固定になりやすいので、入力→部分一致で絞る形にしています。
- 候補クリックで確定用URLに飛び、結果エリアに選択部署を表示します。
- 確定時は
hx-swap-oobで入力欄にも同じ値を反映し、候補欄を空にして「確定した感」を出しています。
④ 取引先(コード+名称)候補(選択時はコード保持)
候補は「コード+名称」で見せつつ、保存に使うのはコード(hidden)にします。
入力を編集し始めたら hidden のコードはクリアして、取り違えを防ぎます。
HTML
<div class="DEMO">
<h4>④ 取引先(コード+名称)候補(選択時はコード保持)</h4>
<p class="HTMX-NOTE">
候補は「コード+名称」で見せつつ、保存に使うのはコード(hidden)にします。<br>
入力を編集し始めたら hidden のコードはクリアして、取り違えを防ぎます。
</p>
<form class="FORM" onsubmit="return false;">
<!-- 送信・保存に使う「取引先コード」 -->
<input id="DEMO_AC_VENDOR2_CODE" type="hidden" name="vendor_code" value="">
<label>
取引先名(候補:コード+名称)
<input
id="DEMO_AC_VENDOR2_INPUT"
type="text"
name="q"
value=""
placeholder="例:V001 / みかん / tanaka…"
autocomplete="off"
oninput="document.getElementById('DEMO_AC_VENDOR2_CODE').value='';"
hx-get="/htmx/demo/_ac_vendor2_suggest.php"
hx-trigger="keyup changed delay:250ms, search"
hx-target="#DEMO_AC_VENDOR2_SUGGEST"
hx-swap="innerHTML"
>
</label>
</form>
<div id="DEMO_AC_VENDOR2_SUGGEST" class="AC_SUGGEST">
<p class="HTMX-NOTE">(ここに候補が表示されます)</p>
</div>
<div id="DEMO_AC_VENDOR2_RESULT" class="CARD RESULT">
<p class="HTMX-NOTE">(ここに選択結果が表示されます)</p>
</div>
</div>
PHP(候補表示/_ac_vendor2_suggest.php)
<?php
// セッションを開始する(統一のため)
session_start();
// 共通データと関数を読み込む
require __DIR__ . '/_ac_data.php';
// 検索語を受け取る
$q = (string)($_GET['q'] ?? '');
// 前後の空白を除去する
$q = trim($q);
// 空なら案内だけ返す
if ($q === '') {
// 入力待ちの案内を返す
echo '<p class="HTMX-NOTE">(入力すると候補が表示されます)</p>';
// ここで終了する
exit;
}
// コード+名称で候補を絞り込む
$list = ac_filter_vendor_coded($AC_VENDORS_CODED, $q, 8);
// 1件もなければ案内を返す
if (count($list) === 0) {
// 見つからない表示を返す
echo '<p class="HTMX-NOTE">候補が見つかりませんでした。</p>';
// ここで終了する
exit;
}
// リストの開始タグを出す
echo '<ul class="AC_LIST">';
// 候補を1件ずつ描画する
foreach ($list as $r) {
// コードを取り出す
$code = (string)($r['code'] ?? '');
// 名称を取り出す
$name = (string)($r['name'] ?? '');
// 1行分を開始する
echo '<li class="AC_ITEM">';
// クリックで確定(コードのみ送ってサーバ側で名称を確定させる)
echo '<button type="button" class="BTN is-sub" hx-get="/htmx/demo/_ac_vendor2_pick.php?code=' . rawurlencode($code) . '" hx-target="#DEMO_AC_VENDOR2_RESULT" hx-swap="innerHTML">';
// 表示は「コード:名称」
echo '<strong>' . h($code) . '</strong>:' . h($name);
// ボタンを閉じる
echo '</button>';
// 1行分を閉じる
echo '</li>';
}
// リストの終了タグを出す
echo '</ul>';
PHP(確定/_ac_vendor2_pick.php)
<?php
// セッションを開始する(統一のため)
session_start();
// 共通データと関数を読み込む
require __DIR__ . '/_ac_data.php';
// 選択された取引先コードを受け取る
$code = (string)($_GET['code'] ?? '');
// 前後の空白を除去する
$code = trim($code);
// 空ならエラーを返す
if ($code === '') {
// エラー表示を返す
echo '<p class="HTMX-NOTE">選択に失敗しました。</p>';
// ここで終了する
exit;
}
// コードから取引先を引く
$row = ac_find_vendor_by_code($AC_VENDORS_CODED, $code);
// 見つからないならエラーを返す
if ($row === null) {
// エラー表示を返す
echo '<p class="HTMX-NOTE">取引先が見つかりませんでした。</p>';
// ここで終了する
exit;
}
// 名称を取り出す
$name = (string)($row['name'] ?? '');
// 選択結果を返す(メインレスポンス)
echo '<p><strong>選択:</strong>' . h($code) . ':' . h($name) . '</p>';
// hiddenの取引先コードをOOBで更新する
echo '<input id="DEMO_AC_VENDOR2_CODE" type="hidden" name="vendor_code" value="' . h($code) . '" hx-swap-oob="outerHTML">';
// 入力欄(名称表示用)をOOBで更新する
echo '<input id="DEMO_AC_VENDOR2_INPUT" type="text" name="q" value="' . h($name) . '" placeholder="例:V001 / みかん / tanaka…" autocomplete="off" oninput="document.getElementById(\'DEMO_AC_VENDOR2_CODE\').value=\'\';" hx-get="/htmx/demo/_ac_vendor2_suggest.php" hx-trigger="keyup changed delay:250ms, search" hx-target="#DEMO_AC_VENDOR2_SUGGEST" hx-swap="innerHTML" hx-swap-oob="outerHTML">';
// 候補欄をOOBでクリアする
echo '<div id="DEMO_AC_VENDOR2_SUGGEST" hx-swap-oob="innerHTML"></div>';
デモ
④ 取引先(コード+名称)候補(選択時はコード保持)
候補は「コード+名称」で見せつつ、保存に使うのはコード(hidden)にします。
入力を編集し始めたら hidden のコードはクリアして、取り違えを防ぎます。
(ここに候補が表示されます)
(ここに選択結果が表示されます)
解説
- 候補は「コード:名称」で表示し、同名が多い取引先でも選び間違えにくくします。
- 候補クリックでは「コードだけ」を送信し、サーバ側でコードから名称を確定して返します(クライアント側の改ざん耐性も上がります)。
- 確定レスポンスで
hx-swap-oobを使い、hiddenに取引先コード、入力欄には名称を反映し、候補欄を同時にクリアします。 - 入力欄を編集し始めたら hidden のコードをクリアし、名称だけ変わってコードが古い という事故を防ぎます(最小JSでOKです)。
次に読むオススメレシピ
参考リンク
このページの著者
経験:Webアプリ/業務システム
得意:PHP・JavaScript・MySQL・CSS
個人実績:フォーム生成基盤/クイズ学習プラットフォーム 等
詳しいプロフィールはこちら! もちもちみかんのプロフィール