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です)。

次に読むオススメレシピ

このページの著者

もちもちみかん(システムエンジニア)

社内SEとしてグループ企業向けの業務アプリを要件定義〜運用まで一気通貫で担当しています。

経験:Webアプリ/業務システム

得意:PHP・JavaScript・MySQL・CSS

個人実績:フォーム生成基盤クイズ学習プラットフォーム

詳しいプロフィールはこちら!  もちもちみかんのプロフィール

もちもちみかん0系くん
TOPへ

もちもちみかん.comとは


このサイトでは、コーディングがめんどうくさい人向けのお助けツールとして、フォームやCSSをノーコードで生成できる、
 もちもちみかん.forms
 もちもちみかん.css1
 もちもちみかん.css2
と言ったジェネレーターを用意してます。

また、このサイトを通じて、「もちもちみかん」のかわいさを普及したいとかんがえてます!