SELECT要素の連動(いくつでも可能&iOS Safariで動作可)

連動させると指数関数的に増えるのも理解できる
Ajaxを推めるのも理解できる
しかし、小さいならこれでも吉とするかな。


optgroupに対してdisplay:noneが効くのならもっと簡単なのに。
iPadとかiPhonesafariに対応させるために面倒だったなと愚痴をこぼす。


3連動させるとして、最後のselect には、onchange は要らない。
optgroupとoptionが共存する時はoptionを消さない
selectedを指定したい時はclass="selected"をつけることで代用
複数のclassを指定するときは最初にselected


構想を練っているときが楽しい。

<!DOCTYPE html>
<meta charset="utf-8">
<title>SELECT要素の連動</title>
<style>
body {
  color: white;
  background: black;
}
</style>

<body>
<form id="SELECT_TEST">
<p>
  <label>Group:</label>
  <select name="group" class="selectA">
    <option value="GroupA">GROUP A</option>
    <option value="GroupB">GROUP B</option>
  </select>

  <label>Team:</label>
  <select name="team" class="selectA">
    <optgroup label="GroupA">
      <option value="TeamA">TEAM A</option>
      <option value="TeamB">TEAM B</option>
    </optgroup>
    <optgroup label="GroupB">
      <option value="TeamC">TEAM C</option>
      <option value="TeamD">TEAM D</option>
    </optgroup>
  </select>

  <label>Player:</label>
  <select name="player" class="selectA">
    <optgroup label="TeamA">
      <option value="PlayerA">PLAYER A</option>
      <option value="PlayerB">PLAYER B</option>
    </optgroup>
    <optgroup label="TeamB">
      <option value="PlayerC">PLAYER C</option>
      <option value="PlayerD">PLAYER D</option>
    </optgroup>
    <optgroup label="TeamC">
      <option value="PlayerE">PLAYER E</option>
      <option value="PlayerF">PLAYER F</option>
    </optgroup>
    <optgroup label="TeamD">
      <option value="PlayerG">PLAYER G</option>
      <option value="PlayerH">PLAYER H</option>
    </optgroup>
  </select>
</p>


<p>
  <label>Group:</label>
  <select name="group" class="selectB">
    <option value="GroupA">GROUP A</option>
    <option value="GroupB" selected>GROUP B</option>
  </select>

  <label>Team:</label>
  <select name="team" class="selectB">
    <optgroup label="GroupA">
      <option value="TeamA">TEAM A</option>
      <option value="TeamB">TEAM B</option>
    </optgroup>
    <optgroup label="GroupB">
      <option value="TeamC">TEAM C</option>
      <option value="TeamD" class="selected">TEAM D</option>
    </optgroup>
  </select>

  <label>Player:</label>
  <select name="player" class="selectB">
    <optgroup label="TeamA">
      <option value="PlayerA">PLAYER A</option>
      <option value="PlayerB">PLAYER B</option>
    </optgroup>
    <optgroup label="TeamB">
      <option value="PlayerC">PLAYER C</option>
      <option value="PlayerD">PLAYER D</option>
    </optgroup>
    <optgroup label="TeamC">
      <option value="PlayerE">PLAYER E</option>
      <option value="PlayerF">PLAYER F</option>
    </optgroup>
    <optgroup label="TeamD">
      <option value="PlayerG">PLAYER G</option>
      <option value="PlayerH">PLAYER H</option>
    </optgroup>
  </select>
</p>

<script>

(function (D) {

  function toAry (nodeList) {
    return Array.prototype.slice.call (nodeList);
  }

  function append (e) {
    this.insertBefore (e, this.firstChild);
  }

  function remove (e) {
    this.removeChild (e);
  }

  function replace (e, n) {
    toAry (e.querySelectorAll ('optgroup')).forEach (remove, e);
    e.appendChild (n);
  }

  //______

  function ESelect (select) {
    this.eSelect = select;
    this.cSelect = select.cloneNode (true);
  }

  function getVal (select) {
    return select.options[select.selectedIndex].value;
  }

  function replaceGroup (label) {
    var s, e;
    var g = this.cSelect.querySelector (label
            ? 'optgroup[label="' + label + '"]'
            : 'optgroup:first-of-type');

    if (g) {
      replace (e = this.eSelect, g.cloneNode (true));
      s = e.querySelector ('option[class^="selected"]') ||
          e.querySelector ('option:first-of-type');
      if (s)
        s.selected = true;
    }
    else
      toAry (this.cSelect.querySelectorAll ('optgroup'))
        .reverse ()
        .forEach (append, e);
  }

  //______
  
  ESelect.prototype.replaceGroup = replaceGroup;
  ESelect.prototype.getVal       = getVal;

  //______
  
  function selectChangeHandle (event) {
    var e = event.target;
    var n, t;
    
    while (-1 < (n = this.selectBuffer.indexOf (e))) {
      t = this.cloneBuffer[n];
      t.replaceGroup (getVal (e));
      e = t.eSelect;
    }
  }


  function init (select) {
    var buf = { }, begin = [ ];
    var i, s, e, n;

    for (i = 0; s = select[i]; i += 1) {
      if ((n = s.className)) {
        if (e = buf[n]) {
          e.addEventListener ('change', this, false);
          this.selectBuffer.push (e);
          this.cloneBuffer.push (new ESelect (s));
          buf[n] = s;
        }
        else
          begin.push (buf[n] = s);
      }
    }

    begin.forEach (function (select) {
      selectChangeHandle.call (this, { target: select });
    }, this);
  }

  //______

  var Interlock = new Object;

  Interlock.handleEvent  = selectChangeHandle;
  Interlock.selectBuffer = [ ];
  Interlock.cloneBuffer  = [ ];
  
  init.call (Interlock, D.querySelectorAll ('select'));
}) (document);

</script>