開発情報・ナレッジ

投稿者: ShiningStar株式会社 2023年11月28日 (火)

JavaScirptでセレクトをドリルダウン表示する

登録フォームブロックにて選択したセレクト項目の内容によって、下位層のセレクト項目の内容を絞り込む方法(ドリルダウン表示)をご紹介します!
今回はjQueryを使わずにJavaScirpt(Vanilla JS)のみで作成しているため、ライブラリ等の読み込みは必要ありません。

また、今回ご紹介するJavaScriptの使用にあたって、HTMLの編集は不要です。


SPIRAL設定例

業種(f01)
職種(f02)
役職(f03)

JavaScriptソース

 // 設定値
  const drdowField = [
    // ①セット
    // ['親要素のname値','子要素のname値',
    //   {
    //     親要素のvalue:[選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue],
    //     親要素のvalue:[選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue],
    //     親要素のvalue:[選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue],
    //   }
    // ],
    // ①セット
    ['f01','f02',
      {
        1:[1,2,3],
        2:[4,5,6],
        3:[7,8,9],
        }
    ],
    ['f02','f03',
      {
        1:[1,2,3],
        2:[4,5,6],
        3:[7,8,9],
      }
    ],
    ['f04','f05',
      {
        1:[1,2,3],
        2:[4,5,6],
        3:[7,8,9],
      }
    ],
    ['f05','f06',
      {
        1:[1,2,3],
        2:[4,5,6],
        3:[7,8,9],
      }
    ],
  ];
// 原則変更不可
// onload時に複数の処理をしたい場合のみ変更あり
window.onload = function () {
  createLabelMappings();
  drdowViewLogic(true);
};

// 下記すべて変更不可
// HTMLからlabelMappingを作成
function createLabelMappings() {
  window.labelMapping = {};

  drdowField.forEach(function (element) {
    const parentSelect = document.getElementsByName(element[0])[0];
    const childSelect = document.getElementsByName(element[1])[0];
 if(parentSelect && childSelect){
    // 親セレクトボックスのラベルマッピング
    window.labelMapping[element[0]] = extractLabels(parentSelect);

    // 子セレクトボックスのラベルマッピング(必要に応じて)
    window.labelMapping[element[1]] = extractLabels(childSelect);
}
  });
}

// セレクトボックスからラベルを抽出(空のvalueを含む)
function extractLabels(selectElement) {
  let labels = {};
  selectElement.querySelectorAll('option').forEach(option => {
    // valueが空でもラベルを保存
    labels[option.value] = option.text;
  });
  return labels;
}

function drdowViewLogic(load = false) {
  drdowField.forEach(function (element, index) {
    var parentField = document.getElementsByName(element[0])[0];
    var childField = document.getElementsByName(element[1])[0];
    var optionList = element[2];

    if (load && parentField && childField) {
      // 初回ロード時には各セットについて子 <select> のオプションを更新
      updateChildOptions(parentField, childField, optionList, index);

      // イベントリスナーを追加して、親 <select> の変更を監視
      parentField.addEventListener('change', function () {
        updateChildOptions(parentField, childField, optionList, index);
      });
    }
  });
}

function updateChildOptions(parentField, childField, optionList, index) {
  var parentValue = parentField.value;

  // 親要素の値が変更された場合、子要素のオプションを更新
  updateOptions(childField, optionList[parentValue]);

  // 子要素が空白のオプションの場合、または既に選択肢が選ばれていない場合、関連する孫要素もリセット
  var childValue = childField.options[childField.selectedIndex]?.value;
  if (!childValue) {
    resetChildOptions(index + 1);
  } else {
    // 子要素に既に値がある場合は、次の関連要素の更新をスキップ
    skipResetForNextChildOptions(index + 1, childValue);
  }
}

function resetChildOptions(startIndex) {
  for (let i = startIndex; i < drdowField.length; i++) {
    let childName = drdowField[i][1];
    let childField = document.getElementsByName(childName)[0];

    // 子要素のオプションを空白にリセット
    updateOptions(childField, []);
  }
}

function skipResetForNextChildOptions(startIndex, existingValue) {
  if (startIndex < drdowField.length) {
    let nextPair = drdowField[startIndex];
    let nextParentField = document.getElementsByName(nextPair[0])[0];
    let nextChildField = document.getElementsByName(nextPair[1])[0];
    let nextOptionList = nextPair[2];

    // 次のセットの親要素が現在の子要素と同じで、かつ既に選択肢が選ばれている場合、リセットしない
    if (nextParentField.name === nextChildField.name && nextChildField.value) {
      updateChildOptions(nextParentField, nextChildField, nextOptionList, startIndex);
    }
  }
}

function updateOptions(select, optionsToShow) {
  // 現在選択されている値を保持
  var existingValue = select.value;

  optionsToShow = optionsToShow || [];

  // 現在のオプションを全て削除
  while (select.firstChild) {
    select.removeChild(select.firstChild);
  }

  // 保存されたラベルマッピングから空白のオプションを追加
  let defaultLabel = window.labelMapping[select.name][""];
  var defaultOption = new Option(defaultLabel || "----- 選択してください -----", "", true);
  select.appendChild(defaultOption);

  // 新しいオプションを追加し、既存の値があればそれを選択
  let foundExistingValue = false;
  if (optionsToShow.length > 0) {
    optionsToShow.forEach(function (val) {
      let label = window.labelMapping[select.name][val];
      var newOption = new Option(label, val);
      select.appendChild(newOption);
      if (val == existingValue) {
        newOption.selected = true;
        foundExistingValue = true;
      }
    });
  }

  select.disabled = optionsToShow.length === 0; // オプションがない場合無効化

  // オプションが更新された後にchangeイベントを発火させる
  if (!foundExistingValue) {
    triggerChangeEvent(select);
  }
}

function triggerChangeEvent(element) {
  var event = new Event('change', { bubbles: true });
  element.dispatchEvent(event);
}

使い方

上記ソースを登録フォームブロックのJavaScriptのタブ内に貼り付けてください。 その上で編集すべき箇所は以下のみとなります。

  const drdowField = [
    // ①セット
    // ['親要素のname値','子要素のname値',
    //   {
    //     親要素のvalue:[選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue],
    //     親要素のvalue:[選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue],
    //     親要素のvalue:[選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue,選択時に表示したい子要素のvalue],
    //   }
    // ],
    // ①セット
    ['f01','f02',
      {
        1:[1,2,3],
        2:[4,5,6],
        3:[7,8,9],
        }
    ],
    ['f02','f03',
      {
        1:[1,2,3],
        2:[4,5,6],
        3:[7,8,9],
      }
    ],
    ['f04','f05',
      {
        1:[1,2,3],
        2:[4,5,6],
        3:[7,8,9],
      }
    ],
    ['f05','f06',
      {
        1:[1,2,3],
        2:[4,5,6],
        3:[7,8,9],
      }
    ],
  ];
抜粋
    ['f01','f02',
      {
        1:[1,2,3],
        2:[4,5,6],
        3:[7,8,9],
        }
    ]
['f01','f02'の箇所は親要素のプルダウンのname値、仮にf01(親階層)を指定し、
その次に子階層となるf02を指定します。
その次の行以降の1:[1,2,3]については、
親(f01)でoptionのvalue値が1の項目が選ばれた時に、
子(f02)では1と2と3が選択可能となる形となります。
この行を増やす事で親が2であれば子は4,5,6と複数の設定が可能となります。
更に
['f02','f03',
      {
        1:[1,2,3],
        2:[4,5,6],
        3:[7,8,9],
      }
    ]
以上の様に続けて['f01','f02'の箇所を['f02','f03'として追加すると
f02が親、f03が子となりf01から見た時に孫となる要素も制御が可能となっております。
このソースでは親や子、孫要素の関係性を持たずに最も下の階層の要素から都度チェックを行い
その親、その次の親とチェックしている為、3階層以上のドリルダウンも自由に設定可能となっております。

親を変更した時に子では選択出来ない値が選択されていた時は
子が空白になり、それ以降の孫要素も空白になるよう設定している為、
DBに入るデータの不整合も防いでおります。

SPIRAL設定例

業種(f01)にてID1を選択した場合
f01でID1の場合はf02の1,2,3が表示される様指定されているので、
3つのみの表示となる。
職種(f02)にてID1を選択した場合
f02でID1の場合はf03の1,2,3が表示される様指定されているので、
3つのみの表示となる。

このJavaScirptは基本的に貼り付けて設定値を変更するだけで自由にドリルダウン表示を実装することができるので、
ぜひお試しください。

解決しない場合はこちら コンテンツに関しての
要望はこちら