htmx 逆引きレシピ
リクエスト中に入力/ボタンを無効化するには?
公開日:
最終更新日:
保存ボタンの連打や、通信中の入力変更は、二重送信や保存競合などの事故につながりがちです。
そこで、通信中だけ入力/ボタンを無効化し、「いま処理中」を明確にします。
このページでは、hx-disabled-elt と hx-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を丸ごと無効化
ここに保存結果が表示されます。
解説
fieldsetをhx-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"を使い、同じグループ内で古いリクエストが結果を上書きしにくい設計にします。- ライブ検索/保存のどちらでも起こり得る “競合” を抑える基本テクです。
参考リンク
このページの著者
経験:Webアプリ/業務システム
得意:PHP・JavaScript・MySQL・CSS
個人実績:フォーム生成基盤/クイズ学習プラットフォーム 等
詳しいプロフィールはこちら! もちもちみかんのプロフィール