[jQuery]選択肢で表示内容を変えていくギミック

見た目は簡単、中身がダルい。

完成イメージ

設問を用意する。

回答すると回答内容に応じたコンテンツを表示する。

表示したコンテンツにも設問があり、以降同様に選んだものに応じて表示していく。

要するにノベルゲーと同じギミックであり、選択肢をリンクにすることで遷移先のページを切り替える感じのものであれば、個人サイトが流行ってたころにたまに見たりした。

今回は1ページ内で完結させるので、js(jQuery)で表示を切り替えていく感じになる。

構築イメージ

後から修正を加えるとか、ギミック自体を使い回せるようにルールとかを決めて組んでいきたい。

選択肢はradioにする。selectだと内訳が見づらいし、buttonとかでも作れるんだけどもとからグループ化されてるradioを採用したほうがいいじゃんね?って感じで。

一旦選んだ項目を再選択できるようにするかどうかは悩みどころ。選択をミスった場合は戻れたほうがいいけど、選んだ先の内容が気に食わない場合に変えられるとなると、例えばノベルゲーであれば都合が良すぎてカタルシスが無いし、アンケートであっても調子が良すぎるのではって感じで。まあ、そこは目的に応じてって感じだろうか。

表示するコンテンツはulで管理する。これもグループ化したいってことで、設問に対するul、回答に対するliって感じで分かりやすいんじゃないかなって。

ギミックのメリット

radioで作るってことから、経路をjsで引っ張れる。ということはフラグ管理ができるので、占い的なものを作るのにも向いてる。

制作前の注意点

選択肢を大量に作ってシナリオ分岐が膨大になるとコンテンツもそれだけのバリエーションが必要になる。程々にするとか、選んだものは違っても表示する内容は同じにするとか、そういう感じで。

やっていく

込み入ってくるのでパーツに分けて書いていく。

基本形

HTMLはこんな感じで、選択肢と表示するコンテンツ。

idだと重複できないし重複したほうが楽な感じがしたので識別はdataを使う。「data-○○のvalueが××」をトリガーにどうのこうのって感じ。

<div data-answer="answer01" class="answerset">
    <label><input type="radio" name="answer01" value="01">01</label>
    <label><input type="radio" name="answer01" value="02">02</label>
    <label><input type="radio" name="answer01" value="03">03</label>
    <label><input type="radio" name="answer01" value="04">04</label>
</div>
<ul data-content="answer01" class="contentlist">
    <li data-select="01">01</li>
    <li data-select="02">02</li>
    <li data-select="03">03</li>
    <li data-select="04">04</li>
</ul>

「data-anser〇〇のvalue××を選んだ場合、data-content○○のdata-select××を表示する」という内容で組む。とりあえずcssでliを非表示にしておく。

ul.contentlist li{
    display: none;
}

jQueryはこうなる。

$(function(){
    //radioを選択した時
    $('input[type="radio"]').on('change',function(){
        //選択したradioを含む.answersetのdata-answerの値を取得
        let answerData = $(this).closest('.answerset').data('answer');
        //選択したradioのvalueを取得
        let answerValue = $(this).val();
        //選択したradioを含む.answerset内のすべてのradioを選択不可にする
        $('.answerset[data-answer="'+answerData+'"]').find('input[type="radio"]').prop('disabled', true);
        //選択に応じたコンテンツを表示する
        $('ul.contentlist[data-content="'+answerData+'"]').find('li[data-select="'+answerValue+'"]').show();
    })
})

余談:nameでもよくないか?という話

.answersetにdataを付けてるけど、radioのnameで管理することは普通にできるわけで。

上記内容では「data-answer○○の中にあるradioをdisabled」ってやってるけど、「選択したradioの親(.answerset)の中にあるradioをdisabled」でも全然いける。

nameで済ませたほうがちょっと文字数が減る。だけど、どっちがスマートなんでしょうね。

<div class="answerset">
    <label><input type="radio" name="answer01" value="01">01</label>
    <label><input type="radio" name="answer01" value="02">02</label>
    <label><input type="radio" name="answer01" value="03">03</label>
    <label><input type="radio" name="answer01" value="04">04</label>
</div>
<ul data-content="answer01" class="contentlist">
    <li data-select="01">01</li>
    <li data-select="02">02</li>
    <li data-select="03">03</li>
    <li data-select="04">04</li>
</ul>
$(function(){
    //radioを選択した時
    $('input[type="radio"]').on('change',function(){
        //選択したradioのnameを取得
        let answerName = $(this).attr('name');
        //選択したradioのvalueを取得
        let answerValue = $(this).val();
        //選択したradioを含む.answerset内のすべてのradioを選択不可にする
        $(this).closest('.answerset').find('input[type="radio"]').prop('disabled', true);
        //選択に応じたコンテンツを表示する
        $('ul.contentlist[data-content="'+answerName+'"]').find('li[data-select="'+answerValue+'"]').show();
    })
})

data-selectの値をユニークにすれば.contentlistのdataも削れるけど、流石にそれ早めたほうがいいと思った。

展開

表示するコンテンツにより設問を変更する場合、例えばこうなる。

<div data-answer="answer01" class="answerset">
    <label><input type="radio" name="answer01" value="01">01</label>
    <label><input type="radio" name="answer01" value="02">02</label>
    <label><input type="radio" name="answer01" value="03">03</label>
    <label><input type="radio" name="answer01" value="04">04</label>
</div>
<ul data-content="answer01" class="contentlist">
    <li data-select="01">01
        <div data-answer="anser02" class="answerset">
            <label><input type="radio" name="answer02" value="01">01</label>
            <label><input type="radio" name="answer02" value="02">02</label>
            <label><input type="radio" name="answer02" value="03">03</label>
            <label><input type="radio" name="answer02" value="04">04</label>
        </div>
    </li>
    <li data-select="02">02</li>
    <li data-select="03">03</li>
    <li data-select="04">04</li>
</ul>
<ul data-content="answer02" class="contentlist">
    <li data-select="01">01</li>
    <li data-select="02">02</li>
    <li data-select="03">03</li>
    <li data-select="04">04</li>
</ul>

表示するliの中に対応する選択肢を入れ込む。コンテンツの方は外に逃がせるので、HTMLの階層はそんなに深くならない。

選択した内容を問わず同一の選択肢を提示する場合はliに入れない。その代わり、選択後に次の設問を表示させたい場合はそうなるようにしておく。

選択項目の集計

radioで選択肢を作ったんで、選択項目を拾ってみる。

今回、発火トリガーはbuttonでやってみる。

<button id="result" type="button">集計</button>

jQueryはこう。

    $('button#result').on('click',function(){
        let array01 = []; //配列
        let array02 = []; //連想配列
        $('.answerset').each(function(){
            key = $(this).data('answer');
            val = $(this).find('input[type="radio"]:checked').val();
            array01.push(val);
            array02[key] = val;
        })
        console.log(array01);
        console.log(array02);
    })

配列と連想配列の2種類。用途によって使い分けが発生する。

選択肢それぞれに数値を割り振って、合計点で表示内容を変える場合は配列。

特定項目の回答内容によって表示内容を変えるなら連想配列。

当然組み合わせてどうのこうのすることもあり得る。

場合によりけり。

上記内容は予め仕込んであるdataとvalueを拾ってあるけど、計測用に別の値を仕込みたいということも考えられる。そうしたいならdataを追加してもいいし、dataとvalの対応表的なものを作って置換してもいいし、みたいな。

選択し直せるようにする場合

これがかなりめんどくさい。やりたいことによってとんでもなくダルい。

まずはおさらいで、この部分が上記で書いてた操作不可の命令。再選択可能にするならまずこれをそもそも設置しない。

$(this).closest('.answerset').find('input[type="radio"]').prop('disabled', true);
1問目からやり直す

簡単。

buttonで発火させるとする。

<button id="reset" type="button">リセット</button>

発火したら全設問を選択していない状態にして、表示しているものを非表示にする。

    $('button#reset').on('click',function(){
        $('.answerset input[type="radio"]').prop('checked',false);
        $('ul.contentlist > li').hide();
    })
任意の箇所で再選択させるギミック

連番とかで制御しないと無理です。

単純に設問を並べただけだと順番の概念がないので、「これ以降をリセット」をすることができない。

なのでまず連番を振る。

        <div data-answer="answer01" class="answerset" data-answernum="1">
            <label><input type="radio" name="answer01" value="01">01</label>
            <label><input type="radio" name="answer01" value="02">02</label>
            <label><input type="radio" name="answer01" value="03">03</label>
            <label><input type="radio" name="answer01" value="04">04</label>
        </div>
        <ul data-content="answer01" class="contentlist">
            <li data-select="01">01</li>
            <li data-select="02">02</li>
            <li data-select="03">03</li>
            <li data-select="04">04</li>
        </ul>
        <div data-answer="answer02" class="answerset" data-answernum="2">
            <label><input type="radio" name="answer02" value="01">01</label>
            <label><input type="radio" name="answer02" value="02">02</label>
            <label><input type="radio" name="answer02" value="03">03</label>
            <label><input type="radio" name="answer02" value="04">04</label>
        </div>
        <ul data-content="answer02" class="contentlist">
            <li data-select="01">01</li>
            <li data-select="02">02</li>
            <li data-select="03">03</li>
            <li data-select="04">04</li>
        </ul>
        <div data-answer="answer03" class="answerset" data-answernum="3">
            <label><input type="radio" name="answer03" value="01">01</label>
            <label><input type="radio" name="answer03" value="02">02</label>
            <label><input type="radio" name="answer03" value="03">03</label>
            <label><input type="radio" name="answer03" value="04">04</label>
        </div>
        <ul data-content="answer03" class="contentlist">
            <li data-select="01">01</li>
            <li data-select="02">02</li>
            <li data-select="03">03</li>
            <li data-select="04">04</li>
        </ul>

「data-answernum」を作った。これはユニークな番号でなく、○問目とかの階層的なもの。だから同階層でコンテンツごとに設問を作れば同じ数字になる。そうしないとリセットの指定ができない。できなくはないけどめんどくさくなる。コンテンツは各設問に紐づいてるので数字を仕込む必要はない。

操作したradioの番号を抽出して以降をリセットする内容はこんな感じ。最初のやつを拡張する感じになる。

    $('input[type="radio"]').on('change',function(){
        //選択したradioの番号を取得
        let answerNum = Number($(this).closest('.answerset').data('answernum'));
        //ページ内の.answersetに処理を行う
        $('.answerset').each(function(){
            //番号を取得
            thisNum = Number($(this).data('answernum'));
            //選択したradioの番号と比較
            if(thisNum > answerNum){
                //選択したradio以降の数字だったら選択解除、コンテンツを非表示
                key = $(this).data('answer');
                val = $(this).find('input[type="radio"]:checked').val();
                $('ul.contentlist[data-content="'+key+'"] > li').hide();
                $(this).find('input[type="radio"]').prop('checked',false);
            }
        })
        let answerData = $(this).closest('.answerset').data('answer');
        let answerValue = $(this).val();
        //(追加)選択したradioに紐づくコンテンツ群を非表示
        $('ul.contentlist[data-content="'+answerData+'"] > li').hide();
        $('ul.contentlist[data-content="'+answerData+'"]').find('li[data-select="'+answerValue+'"]').show();
    })

選択したradioの番号を取得して、以降のradioのcheckedを解除。紐づくコンテンツも非表示。

自身に紐づいてるコンテンツも一旦非表示にしてから表示にする。

超めんどい。

まとめ

こんな感じになった。
https://test.megefeps.info/20230123_change/

jQuery側というかギミック自体というかはそこまで手間はない。ただ、ソースコードのボリュームがえらいことになってしまうことは想像に難くない。

今思いつく範囲での最低限で組んではいるので、class名とかが既存のものと干渉しなければ、実用性的にそこまで引っかかるものはないと思う。

どうでしょうか。

コメント

タイトルとURLをコピーしました