selectタグを用いたステップ型のQ&AをjQueryで実装してみる(備忘録)

今回は、selectタグを用いて、すでに出ている項目のジャンルをプルダウンから選択すると、隠れている次の項目が表示され、その表示された質問の項目をプルダウンから選択すると、その質問の回答結果が表示されるというような実装をしています。

この文章だけだと分かりにくいと思いますので、まずはデモを見ていただくのがよいかと思います。

ただ、自分の場合はこの実装は好きではありません。

なぜかというと、初めから要素を隠した状態にしていたり、ステップを踏むという動作が必要なところが、UI的にあまりよろしくないのかなと思っているからです。

また今回jQueryで実装したのは、JavaScriptへの変換が一部自分には難しいところがあったためです。もっとレベルアップしたら本記事を素のJavaScriptで更新できたらと思っています。

それでは実装に入っていきます。

HTMLの記述について

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="robots" content="index, follow" />
  <title>selectタグを用いたステップ型のQ&A実装</title>
  <link rel="stylesheet" href="../reset.css">
  <link rel="stylesheet" href="style.css">
</head>

<body>
  <div class="bl_question_inner">
    <h1>selectタグを用いたステップ型のQ&A実装</h1>
    <!-- 1つ目 -->
    <div id="genre" class="bl_queation_area">
      <h3 class="el_h3title">項目1について選んでください</h3>
      <div class="bl_select_inner">
        <select id="q_category" aria-label="質問カテゴリー">
          <option value="">----</option>
          <option value="q_category_a">Aについて</option>
          <option value="q_category_b">Bについて</option>
          <option value="q_category_c">Cについて</option>
        </select>
      </div>
    </div>
    <!-- 2つ目 -->
    <div id="question" class="bl_queation_area hide">
      <h3 class="el_h3title">項目2について選んでください</h3>
      <div class="bl_select_inner">
        <!-- Aについて -->
        <select class="hide" id="question_a" aria-label="Aについて">
          <option value="">----</option>
          <option value="question_a01">Aの質問1が入ります</option>
          <option value="question_a02">Aの質問2が入ります</option>
          <option value="question_a03">Aの質問3が入ります</option>
        </select>
        <!-- Bについて -->
        <select class="hide" id="question_b" aria-label="Bについて">
          <option value="">----</option>
          <option value="question_b01">Bの質問1が入ります</option>
          <option value="question_b02">Bの質問2が入ります</option>
          <option value="question_b03">Bの質問3が入ります</option>
        </select>
        <!-- Cについて -->
        <select class="hide" id="question_c" aria-label="Cについて">
          <option value="">----</option>
          <option value="question_c01">Cの質問1が入ります</option>
          <option value="question_c02">Cの質問2が入ります</option>
          <option value="question_c03">Cの質問3が入ります</option>
        </select>
      </div>
    </div>
    <!-- 回答 -->
    <div id="answer" class="bl_answer_area hide">
      <div class="answer">
        <!-- Aについて -->
        <div data-category="question_a">
          <div class="ly_inner hide">
            <p>Aの質問1の回答が入ります</p>
          </div>
          <div class="ly_inner hide">
            <p>Aの質問2の回答が入ります</p>
          </div>
          <div class="ly_inner hide">
            <p>Aの質問3の回答が入ります</p>
          </div>
        </div>
        <!-- Bについて -->
        <div data-category="question_b">
          <div class="ly_inner hide">
            <p>Bの質問1の回答が入ります</p>
          </div>
          <div class="ly_inner hide">
            <p>Bの質問2の回答が入ります</p>
          </div>
          <div class="ly_inner hide">
            <p>Bの質問3の回答が入ります</p>
          </div>
        </div>
        <!-- Cについて -->
        <div data-category="question_c">
          <div class="ly_inner hide">
            <p>Cの質問1の回答が入ります</p>
          </div>
          <div class="ly_inner hide">
            <p>Cの質問2の回答が入ります</p>
          </div>
          <div class="ly_inner hide">
            <p>Cの質問3の回答が入ります</p>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
  <script src="style.js"></script>
</body>

</html>

1つ目の箇所について

idのgenru・q_category、valueの空白、q_category_a〜q_category_cを記載しておく必要があります。任意の名前でOKです。2つ目以降の名前と被らないようにつけます。

<!-- 1つ目 -->
<div id="genre" class="bl_queation_area">
  <h3 class="el_h3title">項目1について選んでください</h3>
  <div class="bl_select_inner">
    <select id="q_category" aria-label="質問カテゴリー">
      <option value="">----</option>
      <option value="q_category_a">Aについて</option>
      <option value="q_category_b">Bについて</option>
      <option value="q_category_c">Cについて</option>
    </select>
  </div>
</div>

2つ目の箇所について

idのquestion、classのhideを付与します。こちらも名前は任意でOKです。hideは要素を隠しておくために付与しています。

<!-- 2つ目 -->
<div id="question" class="bl_queation_area hide">

続いて、同様の考え方で下記のようにid、valueなどの名前をつけていきます。

<!-- Aについて -->
<select class="hide" id="question_a" aria-label="Aについて">
  <option value="">----</option>
  <option value="question_a01">Aの質問1が入ります</option>
  <option value="question_a02">Aの質問2が入ります</option>
  <option value="question_a03">Aの質問3が入ります</option>
</select>
<!-- Bについて -->
<select class="hide" id="question_b" aria-label="Bについて">
  <option value="">----</option>
  <option value="question_b01">Bの質問1が入ります</option>
  <option value="question_b02">Bの質問2が入ります</option>
  <option value="question_b03">Bの質問3が入ります</option>
</select>

回答部分について

最後の回答部分も同じ流れで名前をつけていきます。
注意点としては、2の箇所でつけたidの名前と、今回下記でつけるdata-categoryの名前を一致させる必要があります。

下記の例ではquestion_aを一致させています。

<!-- Aについて -->
<!-- 2つ目 -->
<select class="hide" id="question_a" aria-label="Aについて">
</select>

<!-- 回答 -->
<div data-category="question_a">
  <div class="ly_inner hide">
    <p>Aの質問1の回答が入ります</p>
  </div>
  <div class="ly_inner hide">
    <p>Aの質問2の回答が入ります</p>
  </div>
  <div class="ly_inner hide">
    <p>Aの質問3の回答が入ります</p>
  </div>
</div>

CSSの記述について

@charset "utf-8";

body {
  color: #222;
}
h1 {
  font-size: 20px;
  font-weight: bold;
}
select:focus {
  outline: 3px solid blue;
}
.bl_question_inner {
  width: min(100%, 800px);
  margin: 40px auto;
  padding: 16px;
}

.bl_queation_area {
  background-color: #ccc;
  padding: 24px;
  margin-top: 40px;
}

.bl_queation_area.hide {
  display: none;
}

.el_h3title {
  font-weight: bold;
  margin-bottom: 16px;
}

.bl_select_inner {
  position: relative;
  background: #fff;
  border: 1px solid #222;
  font-size: 14px;
}

.bl_select_inner::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 25px;
  transform: translateY(-50%);
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 10px 8px 0 8px;
  border-color: #222 rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) rgba(0, 0, 0, 0);
}

.bl_select_inner select {
  width: 100%;
  padding: 25px 45px 25px 25px;
}

.bl_select_inner select.hide {
  display: none;
}

.bl_answer_area {
  position: relative;
  padding: 24px;
  border: 1px solid #222;
  margin-top: 40px;
  width: 100%;
  display: flex;
  align-items: center;
}

.bl_answer_area.hide {
  display: none;
}

.bl_answer_area::before {
  content: "A.";
  font-size: 14px;
  color: #222;
  padding-right: 10px;
  line-height: 1.3;
  align-self: flex-start;
}

.ly_inner p {
  font-size: 14px;
}

.ly_inner.hide {
  display: none;
}

ポイントはhideのclassが付与されるときに、要素が非表示になるように設定しているところです。

transitionを効かせたい場合は、display だとうまくいきませんので別アプローチから対応してください。今回は本記事の趣旨と異なるため解説はしません。

.bl_queation_area.hide {
  display: none;
}
.bl_select_inner select.hide {
  display: none;
}
.bl_answer_area.hide {
  display: none;
}
.ly_inner.hide {
  display: none;
}

JavaScriptの記述について

document.addEventListener("DOMContentLoaded", () => {
  const qCategory = document.getElementById("q_category");
  const genre = document.getElementById("genre");
  const question = document.getElementById("question");
  const answer = document.querySelector("#answer");

  // 1つ目
  qCategory.addEventListener("change", () => {
    let q_index = qCategory.selectedIndex;

    answer.classList.add("hide");
    const innerElements = document.querySelectorAll(
      "#answer .answer .ly_inner"
    );
    innerElements.forEach((element) => {
      element.classList.add("hide");
    });

    document.querySelector("#question .bl_select_inner select").value = ""; // セレクトボックス選択解除

    if (q_index !== 0) {
      question.classList.remove("hide");
    } else {
      question.classList.add("hide");
      answer.classList.add("hide");
    }

    select = $("#question .bl_select_inner select").eq(q_index - 1);
    select_id = $(select).attr("id");
    nonselect = $("#question .bl_select_inner select").not(select);

    $(select).removeClass("hide");
    $(nonselect).addClass("hide");
  });

  // 2つ目
  $("#question .bl_select_inner select").on("change", function () {
    a_index = this.selectedIndex;
    if (a_index !== 0) {
      question.classList.remove("hide");
      answer.classList.remove("hide");
    } else {
      question.classList.add("hide");
      answer.classList.add("hide");
    }
    a_select = $('[data-category="' + select_id + '"] .ly_inner');
    a_answers = $(a_select).eq(a_index - 1);
    a_answer = $(a_answers).removeClass("hide");
    $(a_select).not(a_answer).addClass("hide");
  });
});

今回は冒頭にuse strictを宣言すると上手く動作しなかったため省略しています。自分には原因が特定できませんでしたが、今後解決の糸口がつかめたら本記事を更新したいと考えています。

最初に、constで変数を定義して使い回せるようにしています。

下記は1つ目の質問カテゴリーのプルダウンを変更したときに処理されるようになっています。

qCategory.addEventListener("change", () => {
  // 中身
});

下記では、1つ目の質問カテゴリーのプルダウンを変更したときに、2つ目以降の要素がすべて隠れるように設定している箇所です。その後に表示させる処理を行っていきます。

let q_index = qCategory.selectedIndex;

answer.classList.add("hide");
const innerElements = document.querySelectorAll(
  "#answer .answer .ly_inner"
);
innerElements.forEach((element) => {
  element.classList.add("hide");
});

下記で、2つ目を表示させる処理を行っています。
「Aについて」を選んだときにAの質問項目が、「Bについて」を選んだときにBの質問項目が表示されるように対応しています。

eqのところで、q_index – 1としているのは、JavaScriptの配列の順番が0から始まるためです。

通常1番目、2番目と始まるところ、JavaScriptでは0番目、1番目と始まるため、その差異を1ずらすためマイナス1を入れています。

if (q_index !== 0) {
  question.classList.remove("hide");
} else {
  question.classList.add("hide");
  answer.classList.add("hide");
}

select = $("#question .bl_select_inner select").eq(q_index - 1);
select_id = $(select).attr("id");
nonselect = $("#question .bl_select_inner select").not(select);

$(select).removeClass("hide");
$(nonselect).addClass("hide");

下記の2つ目の処理はjQueryでの書き方ですが、1つ目と同様で、2つ目のプルダウンを変更したときに処理されるようになっております。

$("#question .bl_select_inner select").on("change", function () {
  // 中身
});

中身の処理については、1つ目のときとあまり変わりません。
回答部分を2つ目と連動させて表示させるための内容が記述されております。

あまり需要のない記事かもしれませんが、実際に実務で使いましたので備忘録として残しておきました。