HTML 要素の disabled 属性と readonly 属性の違いと正しい使い方

f:id:Naotsugu:20191102202028p:plain


はじめに

HTML 要素の disabled 属性と readonly 属性は、うっかりと間違った使い方をしてしまいがちです。
この記事では属性の挙動の違いを説明し、編集不可コントロールの正しい扱い方について見ていきます。


なお、ここでは以下の HTML をひな形として利用することにします。

<!DOCTYPE html>
<html>
  <meta charset="utf-8">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<body>
  <div class="container m-5">
    <form action="/" method="post">
      ...
      <input type="submit" class="btn btn-primary"/>
    </form>
  </div>
</body>
</html>


<input> 要素の disabled 属性 と readonly 属性の違い

HTML の <input> 要素には disabledreadonly 属性が存在します。

  • disabled : コントロールが無効化され、フォームを Submit しても値の送信が行われない
  • readonly : 値の変更は行えないが、コントロールは有効なので フォームを Submit すれば値の送信が行われる


それぞれのコントロールは以下のような表示となります。

f:id:Naotsugu:20191102160129p:plain

disabled とするとコントロールの見た目がグレーアウトされます。

readonly を指定した場合はコントロールの見た目に変化はなく、フォーカスも当たりますが、値の変更ができません(上記例は Readonly 要素にフォーカスを当てた状態です)。


Bootstrap を使った場合は以下のようになり

  <div class="form-group row">
    <label for="input1" class="col-sm-2 col-form-label">Disabled</label>
    <div class="col-sm-10">
      <input type="text" class="form-control" id="input1" value="Text1" disabled>
    </div>
  </div>
  <div class="form-group row">
    <label for="input2" class="col-sm-2 col-form-label">Readonly</label>
    <div class="col-sm-10">
      <input type="text" class="form-control" id="input2" value="Text2" readonly>
    </div>
  </div>


表示は以下のように双方グレーアウトされた見た目になります(こちらも Readonly 要素にフォーカスを当てた状態です)。

f:id:Naotsugu:20191102160550p:plain

<input> 要素の readonly 属性の例外

テキストコントロール以外の、その他のコントロールにおける readonly 属性 はどのようになるでしょうか。

MDN のドキュメントによると以下の記載となっています。

developer.mozilla.org

メモ: テキスト入力のコントロールのみが読み取り専用にすることができるのに対し、その他のコントロール (チェックボックスやボタンなど) は、読み取り専用と無効化の識別がしにくいので、 readonly 属性は適用されません。


つまりテキスト入力欄以外のコントロールについては以下のように readonly を付けたとしても readonly 属性は適用されません。

  <div class="form-group row">
    <div class="col-sm-3">Checkbox</div>
    <div class="col-sm-9">
      <div class="form-check">
        <input class="form-check-input" type="checkbox" id="check1" name="check1" value="1" readonly>
        <label class="form-check-label" for="check1">checkbox</label>
      </div>
    </div>
  </div>

  <fieldset class="form-group">
    <div class="row">
      <legend class="col-form-label col-sm-3 pt-0">Radios</legend>
      <div class="col-sm-9">
        <div class="form-check">
          <input class="form-check-input" type="radio" name="radios" id="radios1" value="option1" readonly>
          <label class="form-check-label" for="radios1">First radio</label>
        </div>
        <div class="form-check">
          <input class="form-check-input" type="radio" name="radios" id="radios2" value="option2" readonly>
          <label class="form-check-label" for="radios2">Second radio</label>
        </div>
      </div>
    </div>
  </fieldset>


この場合は以下のような表示となり、

f:id:Naotsugu:20191102163512p:plain

チェックの変更もできてしまいます。もちろんフォームを Submit すれば変更した値が送信されてしまいます。

そもそも readonly 属性は無効なので当然ですね。


<select> 要素の readonly 属性

先ほどの checkboxradio と同じように、select 要素にも readonly 属性はありません。


最初に disabled について見てみましょう。

<select name="select" class="custom-select" disabled>
    <option value="1">One</option>
    <option value="2">Two</option>
    <option value="3">Three</option>
</select>

念の為ですが、<select name="select" class="custom-select" disabled="disabled"> でも同じ意味です。


以下のようになり、変更もできなければ、フォームの Submit で値の送信も行われません。

f:id:Naotsugu:20191102004710p:plain


readonly 属性を付けた場合は以下のようになります。

<select name="select" class="custom-select" readonly>
    <option value="1">One</option>
    <option value="2">Two</option>
    <option value="3">Three</option>
</select>


f:id:Naotsugu:20191102004324p:plain

select 要素に readonly 属性は無いので、普通の select 要素として機能してしまいます。


readonly 属性が効かないコントロールで readonly を模倣する4つの方法

select 要素を例に、readonly を模倣する方法を4つ紹介します。


option 要素を disabled 指定する

option 要素に disabled 属性を付けることで readonly を模倣することができます。

<select name="select" class="custom-select">
    <option value="1" disabled>One</option>
    <option value="2" selected>Two</option>
    <option value="3" disabled>Three</option>
</select>


コントロールを選択しようとしても、初期値以外の optiondisabled 指定することで変更できなくなります。

f:id:Naotsugu:20191102010114p:plain


もちろんフォームの Submit で値の送信が行われます。


select コントロールの操作を無効化する

CSS pointer-events: none; を指定することで、マウスクリック操作を無効化できます。

ただしこの場合でも、キーボード操作が可能となるため tabindex="-1" を指定してフォーカスが当たらないようにします。

<select name="select" class="custom-select" style="pointer-events: none;" tabindex="-1">
    <option value="1">One</option>
    <option value="2">Two</option>
    <option value="3">Three</option>
</select>


f:id:Naotsugu:20191102004324p:plain

コントロールの操作ができないので、値の変更が禁止され、readonly のように振る舞うことになります。


disable 指定して hidden で値を送信する

コントロールを disabled 指定し、値の送信は hidden で行うことができます。

<select name="select" class="custom-select" disabled>
    <option value="1">One</option>
    <option value="2" selected>Two</option>
    <option value="3">Three</option>
</select>
<input type="hidden" name="select" value="2">


この場合、以下のような表示になります。

f:id:Naotsugu:20191102171128p:plain


コントロールは無効化されますが、フォームの Submit で hidden 値として送信されます。


disable 指定して Submit 時に有効化する

コントロールを disabled 指定し、Submit 時にコントロールを有効化することができます。

<form id="form" action="/" method="post">
  <div class="form-group">
    <select name="select" class="custom-select" disabled>
      <option value="1">One</option>
      <option value="2">Two</option>
      <option value="3">Three</option>
    </select>
  </div>
  <input type="submit" class="btn btn-primary"/>
</form>


フォームの Submit イベントでコントロールの disabled を変更します。

const form = document.getElementById('form');
form.addEventListener('submit', event => 
    event.target.querySelectorAll("input:disabled, select:disabled")
        .forEach(e => e.disabled = false)
);


フォームの Submit でコントロールが有効化され、値が送信されます。


まとめ

HTML コントロールの disabled 属性 と readonly 属性の違いについて見てきました。

readonly 属性はテキスト項目の input 要素についてのみ有効で、それ以外のコントロールでは特別な対応が必要となり、模倣する4種類の方法を紹介しました。


readonly 指定されたコントロールをグレーアウトするような CSS 定義がされていた場合、値が変更できてしまうことに気付かなかったり、disable 指定と区別がつかずサーバ側に値が渡ってこなくなったりと、間違いやすいところなので、しっかりと憶えておきたいですね。