• jQuery 合計金額計算シミュレーターの実装方法 【チェックボックス ラジオボタン セレクトボックス】

    jQuery

    jQuery 合計金額計算シミュレーターの実装方法 【チェックボックス ラジオボタン セレクトボックス】

    Listen & Subscribe

    Webサイトで商品やサービスを販売する場合など、価格を明示するだけではなく、ユーザーが選択した商品やサービスの合計金額を表示する場合もあるかと思います。

    つまり、 合計金額計算シミュレーターのニーズです。

    しかし、ネット上に様々な実装方法の情報があるものの、「チェックボックス、ラジオボタン、セレクトボックス」の3つの要素があるタイプの合計金額シミュレーターの実装方法については、なかなか情報が見つからず、以前の僕と同じように実装に困っている方もいるかもしれません。

    そこで本記事では、jQueryを使ったチェックボックス、ラジオボタン、セレクトボックスを含む合計金額計算シミュレーターの実装方法を詳しく解説します。

    また、合計金額を3桁のカンマ区切りで表示する方法、全ての値をリセットするボタンの実装方法も紹介しますので、一石三鳥です。

    下記のソースをコピペすることで、まずはプロトタイプを作成することも可能です。

    というわけで、今回はとあるログサウナのオプション金額の計算シミュレーターを例に、早速解説していきます。

    DEMO

    コピペでプロトタイプを作成

    スポンサーリンク

    HTML

    <!DOCTYPE html>
    <html lang="ja">
    <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>合計計算フォームテスト</title>
     <link rel="stylesheet" href="css/style.css">
     <script src="https://code.jquery.com/jquery-3.5.1.min.js"
     integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
    </head>
    <body>
     <section class="simulation">
      <h2>Simulation</h2>
      <div class="box">
       <div class="right">
        <h3>ログサウナカスタムアイテム</h3>
        <section class="section-radio">
         <h4>ログ形状</h4>
         <div class="log">
          <div class="radio" data-price="1000">丸ログ ¥1,000</div>
          <div class="radio" data-price="2000">角ログ ¥2,000</div>
         </div>
        </section>
        <section class="section-radio">
         <h4>サイズ</h4>
         <div class="size">
          <div class="radio" data-price="3000">標準サイズ ¥3,000</div>
          <div class="radio" data-price="4000">コンパクトサイズ ¥4,000</div>
         </div>
        </section>
        <section class="section-radio">
         <h4>ストーブ</h4>
         <div class="stove">
          <div class="radio" data-price="10000">
           <div class="img">
            <img src="https://placehold.jp/100x130.png" alt="">
           </div>
           <div class="text">
            <h5>電気ストーブ</h5>
            <p>テキストテキスト</p>
            <p>¥10,000</p>
           </div>
          </div>
          <div class="radio" data-price="20000">
           <div class="img">
            <img src="https://placehold.jp/100x130.png" alt="">
           </div>
           <div class="text">
            <h5>薪ストーブ</h5>
            <p>テキストテキスト</p>
            <p>¥20,000</p>
           </div>
          </div>
         </div>
        </section>
        <section class="section-check">
         <h4>照明</h4>
         <div class="lighting">
          <div class="check" data-price="5000">照明01 ¥5,000</div>
         </div>
        </section>
        <section class="section-check">
         <h4>塗料</h4>
         <div class="painting">
          <div class="check" data-price="55000">塗料01 ¥55,000</div>
          <div class="check" data-price="50000">塗料02 ¥50,000</div>
          <div class="check" data-price="60000">塗料03 ¥60,000</div>
         </div>
        </section>
        <section class="section-select">
         <h4>運搬費</h4>
         <select class="transport" name="transport">
          <option hidden>地域をお選びください</option>
          <option value="運搬費1 2000" data-price="2000">地域1 ¥2,000</option>
          <option value="運搬費2 3000" data-price="3000">地域2 ¥3,000</option>
          <option value="運搬費3 4000" data-price="4000">地域3 ¥4,000</option>
         </select>
        </section>
       </div>
      </div>
     </section>
     <section class="calc">
      <p>計算結果の表示</p>
      <form class="" method="">
       <input type="text" class="result" name="result" size="30" disabled="disabled">
       <input type="reset" name="reset" class="btn" value="リセット" tabindex="4">
      </form>
     </section>
     <script src="js/index.js"></script>
    </body>
    </html>

    jQuery

    let _click = (window.ontouchstart === undefined)? 'click' : 'touchstart';
    radioPrice = 0;
    checkPrice = 0;
    selectPrice = 0;
    function resultPrice() {
     let totalPrice = radioPrice + checkPrice + selectPrice;
     $('.result').html(totalPrice.toLocaleString());
     $('.result').val(totalPrice.toLocaleString());
    }
    $('.radio').on(_click, function() {
     if($(this).closest('.section-radio').find('.checked').length > 0){
      radioPrice = radioPrice - $(this).closest('.section-radio').find('.checked').data('price');
      radioPrice = radioPrice + $(this).data('price');
      $(this).closest('.section-radio').find('.checked').toggleClass('checked');
      $(this).toggleClass('checked');
     } else {
      radioPrice = radioPrice + $(this).data('price');
      $(this).toggleClass('checked');
     }
     resultPrice();
    })
    $('.check').on(_click, function() {
     if($(this).hasClass('checked')) {
      if(checkPrice > 0) {
       checkPrice = checkPrice - $(this).data('price');
      }
     } else {
      checkPrice = checkPrice + $(this).data('price');
     }
     $(this).toggleClass('checked');
     resultPrice();
    })
    $('[name=transport]').change(function() {
     selectPrice = $('[name=transport] option:selected').data('price');
     resultPrice();
    })
    $('.btn').on(_click, function() {
     $('.check').removeClass('checked');
     $('.radio').removeClass('checked');
     radioPrice = 0;
     checkPrice = 0;
     selectPrice = 0;
     $("select").val('地域をお選びください');
    })

    CSS

    img{
     width: 100%;
     vertical-align: bottom;
    }
    .section-radio,
    .section-check,
    .section-select{
     margin-bottom: 30px;
    }
    .radio,
    .check,
    .transport{
     cursor: pointer;
     position: relative;
    }
    .simulation .box{
     display: flex;
     justify-content: space-between;
     flex-wrap: wrap;
    }
    .simulation .box .left,
    .simulation .box .right{
     width: 50%;
     margin: 0 auto;
    }
    .simulation h2{
     text-align: center;
    }
    .simulation .box .right h4{
     border-bottom: 1px solid #333;
     padding-bottom: 10px;
     margin-bottom: 10px;
    }
    .log .radio{
     display: inline-block;
     margin-right: 10px;
     border: 1px solid #333;
     border-radius: 20px;
     padding: 10px 15px;
     font-size: 14px;
    }
    .log .radio:hover {
     background: #333;
     color:#fff;
    }
    .log .radio.checked {
     background: #333;
     color:#fff;
    }
    .size .radio,
    .roof .radio{
     padding-left: 20px;
    }
    .size .radio::before,
    .roof .radio::before{
     content:'';
     position: absolute;
     left: 0;
     top:50%;
     transform: translateY(-50%);
     -webkit-transform: translateY(-50%);
     -ms-transform: translateY(-50%);
     width: 10px;
     height: 10px;
     border-radius: 100%;
     border: 1px solid #333;
    }
    .size .radio.checked::before,
    .roof .radio.checked::before{
     background: #333;
    }
    .stove,.bench{
     display: flex;
     justify-content: space-between;
     flex-wrap: wrap;
    }
    .stove .radio,
    .bench .radio{
     width: 48%;
     border: 1px solid #333;
     border-radius: 20px;
     display: flex;
     justify-content: space-between;
     padding: 15px;
     box-sizing: border-box;
     margin-bottom: 10px;
    }
    .stove .radio .img,
    .bench .radio .img{
     width: 60%;
    }
    .stove .radio .text,
    .bench .radio .text{
     width: 35%;
    }
    .stove .radio.checked,
    .bench .radio.checked{
     border-color: #fff;
    }
    .stove .radio.checked::before,
    .bench .radio.checked::before{
     border: 2px solid blue;
     content:'';
     position: absolute;
     top:0;
     left: 0;
     width: 100%;
     height: 100%;
     border-radius: 20px;
     box-sizing: border-box;
    }
    .lighting .check::before,
    .painting .check::before,
    .construction .check::before{
     content:'';
     position: absolute;
     left: 0;
     top:50%;
     transform: translateY(-50%);
     -webkit-transform: translateY(-50%);
     -ms-transform: translateY(-50%);
     width: 10px;
     height: 10px;
     border: 1px solid #333;
    }
    .lighting .check,
    .painting .check,
    .construction .check{
     padding-left: 20px;
    }
    .lighting .check.checked::after,
    .painting .check.checked::after,
    .construction .check.checked::after{
     content:'';
     position: absolute;
     left: -2px;
     top: 47%;
     transform: translateY(-50%);
     -webkit-transform: translateY(-50%);
     -ms-transform: translateY(-50%);
     background-image: url(../img/check.png);
     background-position: center;
     background-repeat: no-repeat;
     background-size: contain;
     width: 16px;
     height: 16px;
    }
    select.transport {
     width: 100%;
     padding: 15px 20px;
     box-sizing: border-box;
    }
    
    .calc{
     position:fixed;
     left: 0;
     top:50%;
    }
    .btn{
     cursor: pointer;
     display: block;
     margin-top: 10px;
    }
    @media screen and (max-width:768px) {
     .simulation .box .left,
     .simulation .box .right{
      width: 100%;
      padding: 0 20px;
      box-sizing: border-box;
     }
    }

    以上になります。

    上記のコードをコピペし、適宜階層を合わせ各ファイルを読み込んでください。

    下記デモページがそのまま表示されるはずです。

    DEMO

    次のセクションでは、各コードを深掘りしつつ、1行目から解説していきます。

    合計金額計算の実装 チェックボックス編

    $('.check').on(_click, function() {
     if($(this).hasClass('checked')) {
      if(checkPrice > 0) {
       checkPrice = checkPrice - $(this).data('price');
      }
     } else {
      checkPrice = checkPrice + $(this).data('price');
     }
     $(this).toggleClass('checked');
     resultPrice();
    })

    チェックボックスがクリックされたときに、その選択状態に応じて、価格を計算するためのものです。

    • 1行目: .checkクラスの要素に、クリックイベントが発生した場合に、関数が実行されるように設定しています。
    • 2行目: クリックされたチェックボックスに.checkedクラスが付与されている場合に、以下の処理を実行します。
    • 3行目: 変数checkPriceが0より大きい場合に、クリックされたチェックボックスのdata-price属性の値を、変数checkPriceから減算します。
    • 6行目: クリックされたチェックボックスに.checkedクラスが付与されていない場合に、以下の処理を実行します。
    • 7行目: クリックされたチェックボックスのdata-price属性の値を、変数checkPriceに加算します。
    • 9行目: クリックされたチェックボックスに.checkedクラスをトグルします。
    • 10行目: resultPrice()関数を呼び出し、合計金額を再計算します。

    チェックボックスを選択するたびに価格を正しく計算し、選択されたチェックボックスに.checkedクラスを付与することで、視覚的に選択されていることをユーザーに示しています。

    また、変数checkPriceが0より大きい場合にのみ、クリックされたチェックボックスの価格を減算することで、チェックボックスの価格が負の値になることを防止しています。

    合計金額計算の実装 ラジオボタン編

    $('.radio').on(_click, function() {
     if($(this).closest('.section-radio').find('.checked').length > 0){
      radioPrice = radioPrice - $(this).closest('.section-radio').find('.checked').data('price');
      radioPrice = radioPrice + $(this).data('price');
      $(this).closest('.section-radio').find('.checked').toggleClass('checked');
      $(this).toggleClass('checked');
     } else {
      radioPrice = radioPrice + $(this).data('price');
      $(this).toggleClass('checked');
     }
     resultPrice();
    })

    ラジオボタンがクリックされたときに、その選択状態に応じて、価格を計算するためのコードです。

    • 1行目: .radioクラスの要素に、クリックイベントが発生した場合に、関数が実行されるように設定しています。
    • 2行目: 選択されたラジオボタンの親要素(.section-radio)内で、クラス.checkedを持つ要素の数が1つ以上ある場合に、以下の処理を実行します。
    • 3行目: 選択されたラジオボタンの親要素内で、クラス.checkedを持つ要素のdata-price属性の値を、変数radioPriceから減算します。
    • 4行目: 選択されたラジオボタンのdata-price属性の値を、変数radioPriceに加算します。
    • 5行目: 選択されたラジオボタンの親要素内で、クラス.checkedを持つ要素から.checkedクラスを削除し、選択されたラジオボタンに.checkedクラスを付与します。
    • 6行目: .checkedクラスをトグルします。
    • 7行目: ラジオボタンが初めて選択された場合に、以下の処理を実行します。
    • 8行目: 選択されたラジオボタンのdata-price属性の値を、変数radioPriceに加算します。
    • 9行目: .checkedクラスをトグルします。
    • 11行目: resultPrice()関数を呼び出し、合計金額を再計算します。

    ラジオボタンを選択するたびに価格を正しく計算し、選択されたラジオボタンに.checkedクラスを付与することで、視覚的に選択されていることをユーザーに示しています。

    合計金額計算の実装 セレクトボックス編

    $('[name=transport]').change(function() {
     selectPrice = $('[name=transport] option:selected').data('price');
     resultPrice();
    })

    選択された地域の運搬費を取得して、合計金額を再計算するためのものです。

    • 1行目: [name=transport]セレクタの要素に、changeイベントが発生した場合に、関数が実行されるように設定しています。
    • 2行目: 選択されたオプションのdata-price属性の値を、変数selectPriceに代入します。
    • 3行目: resultPrice()関数を呼び出し、合計金額を再計算します。

    セレクトボックスの選択を監視し、選択されたオプションの価格を正しく取得して、合計金額を再計算するために使用されます。

    option:selectedセレクタを使用して、選択されたオプション要素に対して、data-price属性の値を取得している点が重要です。

    合計金額の算出と3桁カンマ区切りの表示方法

    function resultPrice() {
     let totalPrice = radioPrice + checkPrice + selectPrice;
     $('.result').html(totalPrice.toLocaleString());
     $('.result').val(totalPrice.toLocaleString());
    }

    チェックボックス、ラジオボタン、セレクトボックスの値を取得し、合計金額を算出し、表示するためのものです。

    • 2行目: 変数totalPriceを宣言し、radioPricecheckPriceselectPriceの値を足して代入します。
    • 3行目: totalPriceを3桁カンマ区切りの文字列に変換し、HTML要素.resultのテキストとして設定します。
    • 4行目: totalPriceを3桁カンマ区切りの文字列に変換し、HTML要素.resultの値として設定します。

    jQueryを使用して、チェックボックス、ラジオボタン、セレクトボックスで選択した全ての価格を加算し、3桁カンマ区切りの表示形式に変換して、合計金額を表示するためのものです。

    また、toLocaleString()メソッドを使用することで、ブラウザのロケールに基づいた書式で数値を表示できるため、国際化にも対応しています。

    リセットボタンを設定する

    $('.btn').on(_click, function() {
     $('.check').removeClass('checked');
     $('.radio').removeClass('checked');
     radioPrice = 0;
     checkPrice = 0;
     selectPrice = 0;
     $("select").val('地域をお選びください');
    })

    合計金額をリセットするための処理です。

    • 2、3行目: .checkクラスと.radioクラスを持つHTML要素から、checkedクラスを削除します。
    • 4、5、6行目: radioPricecheckPriceselectPriceの値を0にリセットします。
    • 7行目: select要素の選択状態を初期値に戻します。

    リセット処理に必要な要素に対して、操作を行っています。

    ただ、問題はありませんが、リセットボタンの実装を見た場合に、どのような処理が行われるかがすぐに理解できるように、ボタンのラベルなども適切に設定するとより良いかもしれません。

    おまけ

    let _click = (window.ontouchstart === undefined)? 'click' : 'touchstart';

    jQueryの一番上に書いてあったこのコードについて解説します。

    _clickという変数に代入された値を元に、クリックまたはタッチイベントを判定するためのものです。

    具体的には、window.ontouchstartundefinedであれば、つまりタッチイベントがサポートされていない場合は_click変数に'click'を代入し、クリックイベントを使います。

    一方、タッチイベントがサポートされている場合は_click変数に'touchstart'を代入し、タッチイベントを使います。

    このようにすることで、デバイスの種類に応じて適切なイベントを使用できるようになります。

    UXの改善の依頼はこちらから