htmx 逆引きレシピ
リクエスト中に入力/ボタンを無効化するには?

公開日:
最終更新日:

保存ボタンの連打や、通信中の入力変更は、二重送信や保存競合などの事故につながりがちです。
そこで、通信中だけ入力/ボタンを無効化し、「いま処理中」を明確にします。

このページでは、hx-disabled-elthx-sync を使って、連打防止fieldset丸ごと無効化多重リクエスト抑制をまとめて試せます。
管理画面でそのまま使える“事故らない保存UI”の作り方です。

使用するhtmx属性

  • hx-post:POSTで保存し、結果HTMLを返して差し替える(保存/更新向き)
  • hx-trigger:リクエストを発火するタイミングを指定(例:submit / click
  • hx-target:返ってきたHTMLを差し替える先を指定(結果エリアだけ更新)
  • hx-swap:差し替え方を指定(例:innerHTML
  • hx-disabled-elt:通信中だけ disabled にする要素を指定(ボタン/fieldsetなど)
  • hx-sync:同じグループ内の多重リクエストを同期し、古い結果で上書きされる事故を防ぐ(例::replace

利用シーン

  • 「連打事故防止」:保存ボタンを連打されても、二重登録や二重更新を起こしたくない
  • 「保存中はfieldsetを丸ごと無効化」:入力が多いフォームで、保存中の編集や競合を防ぎたい
  • 「多重リクエストを抑える」:遅い通信→速い通信の順で押されても、古い結果で上書きされたくない

① 連打事故防止(ボタンだけ無効化)

保存ボタンを押した瞬間、ボタンだけ disabled にして連打を防ぎます。
無効化する要素を hx-disabled-elt で指定します。

HTML

<div class="DEMO">

	<h4>① 連打事故防止(ボタンだけ無効化)</h4>

	<form
		id="DEMO_DISABLED_1_FORM"
		class="FORM"
		method="post"
		hx-post="/htmx/demo/_disabled_elt_save_1.php"
		hx-trigger="submit"
		hx-target="#DEMO_DISABLED_1_RESULT"
		hx-swap="innerHTML"
		hx-disabled-elt="#DEMO_DISABLED_1_BTN"
	>
		<label>
			タイトル
			<input type="text" name="title" value="" placeholder="例:申請:メーリングリスト追加">
		</label>

		<button id="DEMO_DISABLED_1_BTN" class="BTN is-ok" type="submit">
			保存(連打してみてOK)
		</button>
	</form>

	<div id="DEMO_DISABLED_1_RESULT" class="CARD">
		<p class="HTMX-NOTE">ここに保存結果が表示されます。</p>
	</div>

</div>

PHP

<?php
// 型を厳密に扱う
declare(strict_types=1);

// HTMLとして返す
header('Content-Type: text/html; charset=UTF-8');

// HTMLエスケープ関数を用意する
function h(string $s): string {
	// 特殊文字をエスケープする
	return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

// タイトルを受け取る
$title = (string)($_POST['title'] ?? '');

// 前後空白を除去する
$title = trim($title);

// タイトルが空なら補正する
if ($title === '') $title = '申請:新規作成';

// 通信が遅い想定で少し待つ
usleep(900000);

// 新しいIDを作る
$newId = random_int(1000, 99999);

// 現在時刻を作る
$now = date('H:i:s');
?>
<div class="FORM-RESULT is-ok">
	<strong>保存しました(ボタンだけ無効化)</strong>
	<div>ID:<?= h((string)$newId) ?></div>
	<div>タイトル:<?= h($title) ?></div>
	<div>時刻:<?= h($now) ?></div>
	<p class="HTMX-NOTE">通信中は保存ボタンが無効化され、連打事故を防げます。</p>
</div>

デモ

① 連打事故防止(ボタンだけ無効化)

ここに保存結果が表示されます。

解説

  • 保存ボタンだけを hx-disabled-elt で指定し、通信中は disabled にします。
  • 入力は触れるままなので、軽い保存フォームに向いています。
  • 「二重送信だけ防ぎたい」場面で、最小コストで導入できます。

② 保存中はfieldsetを丸ごと無効化

入力欄が多いフォームでは、保存中に変更されると事故りやすいです。
そこで fieldset を丸ごと無効化して、保存中は操作できない状態にします。

HTML

<div class="DEMO">

	<h4>② 保存中はfieldsetを丸ごと無効化</h4>

	<form
		id="DEMO_DISABLED_2_FORM"
		class="FORM"
		method="post"
		hx-post="/htmx/demo/_disabled_elt_save_2.php"
		hx-trigger="submit"
		hx-target="#DEMO_DISABLED_2_RESULT"
		hx-swap="innerHTML"
		hx-disabled-elt="#DEMO_DISABLED_2_FIELDSET"
	>
		<fieldset id="DEMO_DISABLED_2_FIELDSET" class="CARD">
			<legend><strong>保存フォーム</strong></legend>

			<label>
				タイトル
				<input type="text" name="title" value="" placeholder="例:申請:端末貸与">
			</label>

			<label>
				担当
				<input type="text" name="owner" value="" placeholder="例:tanaka">
			</label>

			<button class="BTN is-ok" type="submit">
				fieldsetごと無効化して保存
			</button>

			<p class="HTMX-NOTE">
				保存中は入力もボタンも止まるので、「保存処理と編集が競合」しにくくなります。
			</p>
		</fieldset>
	</form>

	<div id="DEMO_DISABLED_2_RESULT" class="CARD">
		<p class="HTMX-NOTE">ここに保存結果が表示されます。</p>
	</div>

</div>

PHP

<?php
// 型を厳密に扱う
declare(strict_types=1);

// HTMLとして返す
header('Content-Type: text/html; charset=UTF-8');

// HTMLエスケープ関数を用意する
function h(string $s): string {
	// 特殊文字をエスケープする
	return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

// タイトルを受け取る
$title = (string)($_POST['title'] ?? '');

// 担当を受け取る
$owner = (string)($_POST['owner'] ?? '');

// 前後空白を除去する
$title = trim($title);

// 前後空白を除去する
$owner = trim($owner);

// タイトルが空なら補正する
if ($title === '') $title = '申請:新規作成';

// 担当が空なら補正する
if ($owner === '') $owner = 'tanaka';

// 通信が遅い想定で少し待つ
usleep(1100000);

// 新しいIDを作る
$newId = random_int(1000, 99999);

// 現在時刻を作る
$now = date('H:i:s');
?>
<div class="FORM-RESULT is-ok">
	<strong>保存しました(fieldset丸ごと無効化)</strong>
	<div>ID:<?= h((string)$newId) ?></div>
	<div>タイトル:<?= h($title) ?></div>
	<div>担当:<?= h($owner) ?></div>
	<div>時刻:<?= h($now) ?></div>
	<p class="HTMX-NOTE">保存中は入力欄も止まるので、保存処理と編集の競合を防げます。</p>
</div>

デモ

② 保存中はfieldsetを丸ごと無効化

保存フォーム

保存中は入力もボタンも止まるので、「保存処理と編集が競合」しにくくなります。

ここに保存結果が表示されます。

解説

  • fieldsethx-disabled-elt に指定し、保存中は入力もボタンも止めます。
  • 保存中に値を変えられると、ユーザーも「どっちが保存された?」となりがちです。
  • 入力項目が多い管理画面/申請フォームで特に効果があります。

③ 多重リクエストを抑える(hx-sync)

「遅いリクエスト → すぐ次のリクエスト」を起こすと、古いレスポンスで上書きされることがあります。
hx-sync を使うと、同じグループ内のリクエストを同期でき、古い結果で上書きされる事故を防げます

HTML

<div class="DEMO">

	<h4>③ 多重リクエストを抑える(hx-sync)</h4>

	<form id="DEMO_DISABLED_3_FORM" class="FORM" method="post">
		<label>
			タイトル
			<input type="text" name="title" value="" placeholder="例:申請:権限変更">
		</label>

		<div id="DEMO_DISABLED_3_SYNC_GROUP" class="CARD">
			<p class="HTMX-NOTE">
				「遅い保存」→すぐ「速い保存」を押しても、結果は“速い保存”が勝ちます。
			</p>

			<div style="display:flex; gap:.6rem; flex-wrap:wrap;">
				<button
					type="button"
					class="BTN is-ghost"
					hx-post="/htmx/demo/_disabled_elt_race.php?mode=slow"
					hx-include="#DEMO_DISABLED_3_FORM"
					hx-trigger="click"
					hx-target="#DEMO_DISABLED_3_RESULT"
					hx-swap="innerHTML"
					hx-sync="#DEMO_DISABLED_3_SYNC_GROUP:replace"
				>
					遅い保存(1.2秒)
				</button>

				<button
					type="button"
					class="BTN is-ok"
					hx-post="/htmx/demo/_disabled_elt_race.php?mode=fast"
					hx-include="#DEMO_DISABLED_3_FORM"
					hx-trigger="click"
					hx-target="#DEMO_DISABLED_3_RESULT"
					hx-swap="innerHTML"
					hx-sync="#DEMO_DISABLED_3_SYNC_GROUP:replace"
				>
					速い保存(0.2秒)
				</button>
			</div>
		</div>
	</form>

	<div id="DEMO_DISABLED_3_RESULT" class="CARD">
		<p class="HTMX-NOTE">ここに保存結果が表示されます。</p>
	</div>

</div>

PHP

<?php
// 型を厳密に扱う
declare(strict_types=1);

// HTMLとして返す
header('Content-Type: text/html; charset=UTF-8');

// HTMLエスケープ関数を用意する
function h(string $s): string {
	// 特殊文字をエスケープする
	return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

// モードを受け取る(slow/fast)
$mode = (string)($_GET['mode'] ?? 'fast');

// タイトルを受け取る
$title = (string)($_POST['title'] ?? '');

// 前後空白を除去する
$title = trim($title);

// タイトルが空なら補正する
if ($title === '') $title = '申請:新規作成';

// 遅延時間(マイクロ秒)を決める
$wait = ($mode === 'slow') ? 1200000 : 200000;

// 通信が遅い/速いを再現する
usleep($wait);

// 新しいIDを作る
$newId = random_int(1000, 99999);

// 現在時刻を作る
$now = date('H:i:s');

// モード表示用文字列を作る
$label = ($mode === 'slow') ? '遅い保存(1.2秒)' : '速い保存(0.2秒)';
?>
<div class="FORM-RESULT is-warn">
	<strong><?= h($label) ?> が完了しました</strong>
	<div>ID:<?= h((string)$newId) ?></div>
	<div>タイトル:<?= h($title) ?></div>
	<div>時刻:<?= h($now) ?></div>
</div>

デモ

③ 多重リクエストを抑える(hx-sync)

「遅い保存」→すぐ「速い保存」を押しても、結果は“速い保存”が勝ちます。

ここに保存結果が表示されます。

解説

  • 「遅い保存」→すぐ「速い保存」のように、複数リクエストが重なる状況を再現します。
  • hx-sync="...:replace" を使い、同じグループ内で古いリクエストが結果を上書きしにくい設計にします。
  • ライブ検索/保存のどちらでも起こり得る “競合” を抑える基本テクです。

このページの著者

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

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

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

得意:PHP・JavaScript・MySQL・CSS

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

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

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

もちもちみかん.comとは


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

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