cloneNode() で複製した select 要素の選択状況がリセットされる

セレクトボックスを cloneNode() して、その要素を appendChild() とか replaceChild() とかすると、複製する前に選択していた状態が再現されなかった。

cloneNode() したフォーム部品が、直前に画面上でどのように操作されていたか、という情報は、ブラウザによって引き継いだり引き継がなかったりするようだ。チェックボックスやラジオボタンのチェック状況なんかも同様のことが起こったりするみたい。

で、これをどう回避したらいいかというと、元の select 要素から value 属性を移植 してやれば、プルダウンのどの項目を選択した状態にするかを設定してやれる。

コードで実例

例えば以下のようなプルダウンメニューがあったとして、項目の「その2」とかを画面上で選んだとする。その後に何らかの操作をするとこのセレクトボックスが複製されて隣に表示されるのだが、何にも考慮していないと「指定なし」を選択した状態のプルダウンを表示するブラウザがある。

<div id="parent">
  <select id="select">
    <option value="" selected>指定なし</option>
    <option value="1">その1</option>
    <option value="2">その2</option>
    <option value="3">その3</option>
  </select>
</div>

そこで、cloneNode() するときに value 値をコピーするひと手間を加えてやる。

function cloneSelectBox() {
  // 元の要素を取得しコピーを生成する
  var src = document.getElementById("select");
  var copy = src.cloneNode(true);
  
  // 元の選択状態をコピーする
  copy.value = src.value;
  
  // 元の要素と入れ替える
  var parent = document.getElementById("parent");
  parent.replaceChild(copy, src);
}

これで OK。

select 要素の value 属性と option 要素の selected 属性

select 要素の value 属性は、現在選択されている option 要素の value 属性値を持つのだが、「現在選択されている option 要素」には selected 属性が付与されている

これは HTML ソース上に selected 属性がコーディングされているかどうかではなく、ブラウザ上で選択しているアクティブな項目の情報が、select 要素の value 属性と option 要素の selected 属性とで同時に示されている。

だから、select 要素の value 属性をコピーするのではなく、各 option 要素の selected 属性をコピーしてあげても同様の結果を得られる。

function cloneSelectBox() {
  // 元の要素を取得しコピーを生成する
  var src = document.getElementById("select");
  var copy = src.cloneNode(true);
  
  // option 要素の選択状況をコピーする
  var copyOptions = copy.getElementsByTagName("option");
  var srcOptions = src.getElementsByTagName("option");
  for(var i = 0; i < copyOptions.length; i++) {
    var copyElem = copyOptions[i];
    for(var j = 0; j < srcOptions.length; j++) {
      var srcElem = srcOptions[j];
      // コピー前と要素の value 属性値を比較して
      // 同じなら selected 属性値をコピーしてやる
      if(copyElem.value == srcElem.value) {
        copyElem.selected = srcElem.selected;
      }
    }
  }
  
  // 元の要素と入れ替える
  var parent = document.getElementById("parent");
  parent.replaceChild(copy, src);
}

こんな風にしても意図した動作になってくれる。まぁでも面倒臭いので最初の方のやり方で良いのでは。

自分が試した環境では、チェックボックス、ラジオボタンは問題なし

先程「チェックボックスやラジオボタンのチェック状況なんかも同様のことが起こったりするみたい。」と書いたが、自分が試したいくつかの環境では特に問題なかった。

ただし、ラジオボタンをただ複製して配置した場合は、同じ value 属性のラジオボタンが複数存在することになるので、選択状態がおかしくなる。複製したら value 属性値を変更してから Append するようにしたい。