Search code examples
javascriptjquerycss

How to create a clickable stepper


I am looking for a solution to create a dynamic stepper. if i select Heudebouville and then feedbackDo, all the checkboxes between should be checked, that's good for the moment, but if i want to deselect one of the checkboxes between (for example : Babille) it should be deselect (checked=false without changing the classNames), but it's not the case here.

in the example below, i checked the first checkbox and another one, the checkboxes between are checked (it's good) but when i try to deselect them ( Post a contest or Provide feedback) they still checked.

enter image description here I really hope it's clear Here my code :

  // Switch cites
  $("#fluxLibreSwitchCity").click(function () {
    var fromCity = $(".FluxLibreStep2__From .FluxLibreStep2__City");
    var toCity = $(".FluxLibreStep2__To .FluxLibreStep2__City");

    // Inverse  cities
    var tempCity = fromCity.text();
    fromCity.text(toCity.text());
    toCity.text(tempCity);

    // Inverse passages
    var passages = $(".StepProgress > li");
    var reversedPassages = passages.toArray().reverse();
    $(".StepProgress").html(reversedPassages);
    $(".StepProgress").toggleClass("inverse");
    redrawSteps();
    CalculatePrice();
  });

  // Road
  // Calculate prices
  const CalculatePrice = () => {
    $('input[type="checkbox"]').change(function () {
      var totalPrice = 0;
      $('input[type="checkbox"]:checked').each(function () {
        var price = parseFloat(
          $(this).siblings("label").find(".infoORprice").text().trim()
        );
        totalPrice += price;
      });
      $("#totalPrice").text(totalPrice.toFixed(2) + " €");
    });
  };
  CalculatePrice();

  // order checkbox
  var checkboxes = document.querySelectorAll(
    '.StepProgress-item input[type="checkbox"]'
  );

  checkboxes.forEach((checkbox, index) => {
    checkbox.addEventListener("change", function () {
      redrawSteps();
    });
  });
  const redrawSteps = () => {
    const bluePath = document.querySelectorAll(".StepProgress-item");
    bluePath.forEach((li) => li.classList.remove("is-active"));

    const checked = document.querySelectorAll(
      '.StepProgress-item input[type="checkbox"]:checked'
    );

    if (checked.length > 1) {
      const first = checked[0].closest("li");
      const last = checked[checked.length - 1].closest("li");
      let started = false;
      let finished = false;

      bluePath.forEach((li) => {
        if (li === first) {
          started = true;
        } else if (li === last) {
          finished = true;
        }

        if (started && !finished) {
          const checkbox = li.querySelector('input[type="checkbox"]');
          li.classList.add("is-active");
          if (checkbox && !li.classList.contains("subItem")) {
            checkbox.checked = true;
          }
        }
      });
    }
    var allItems = document.querySelectorAll(".StepProgress-item");
    allItems.forEach(function (item) {
      var checkbox = item.querySelector('input[type="checkbox"]');
      if (checkbox && checkbox.checked) {
        item.classList.add("selected");
      } else {
        item.classList.remove("selected");
      }
    });
  };
.StepProgress {
  position: relative;
  display: flex;
  flex-direction: column;
  list-style: none;
  width: 100%;
  padding: 0;
}
.StepProgress:before {
  display: inline-block;
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: 10px;
  height: calc(100% - 90px);
  border-left: 4px solid gray;
  transform: translateY(-50%);
  z-index: 1;
}
.StepProgress__FromToCity {
  display: flex !important;
  align-items: center;
  gap: 5px;
  font-size: 14px;
  color: gray;
  margin-left: -6px !important;
  margin-bottom: 5px !important;
}
.StepProgress__FromToCity:before {
  content: "";
  display: block;
  background-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15 7.5c0 4.5-6 9-6 9s-6-4.5-6-9a6 6 0 1 1 12 0' stroke='%23787878' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M9 9.75a2.25 2.25 0 1 0 0-4.5 2.25 2.25 0 0 0 0 4.5' stroke='%23787878' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  width: 18px;
  height: 18px;
}
.StepProgress__FromToCity:last-child {
  margin-top: -10px;
}
.StepProgress-item {
  position: relative;
  display: flex !important;
  padding-bottom: 20px !important;
  height: 100px;
}
.StepProgress-item input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 20px;
  height: 20px;
  border: 2px solid gray;
  border-radius: 50%;
  margin-right: 10px;
  background-color: white;
  position: relative;
  top: 6px;
  left: -8px;
  z-index: 2;
}
.StepProgress-item input[type="checkbox"]:checked {
  box-shadow: 0px 9px 17px -4px gray;
  border: 2px solid blue;
}
.StepProgress-item input[type="checkbox"]:checked:before {
  content: "";
  background-color: blue;
}
.StepProgress-item input[type="checkbox"]:checked:after {
  content: "";
  display: block;
  width: 100%;
  height: 100%;
  background: #e0f8ff;
  border-radius: 50px;
}
.StepProgress-item input[type="checkbox"]:before {
  content: "";
  display: block;
  width: 10px;
  height: 10px;
  background-color: gray;
  border-radius: 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.StepProgress-item__subItemPassageNum {
  display: contents;
}
.StepProgress-item__InfoPassage {
  display: flex;
  flex-direction: column;
  flex: 1;
  color: black;
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 0;
}
.StepProgress-item__InfoPassage span {
  font-size: 14px;
  font-weight: 400;
}
.StepProgress-item__InfoPassage .infoORprice {
  color: gray;
}
.StepProgress-item__InfoPassage .icon {
  position: absolute;
  right: 0;
  color: blue;
  font-size: 14px;
  font-weight: 600;
  border-radius: 3px;
  background-color: rgba(204, 204, 204, 0.6);
  align-self: baseline;
  padding: 2px 3px;
}
.StepProgress-item__InfoPassage .icon::before {
  margin-right: 5px;
}
.StepProgress-item.selected > * {
  z-index: 1;
}
.StepProgress-item.selected:after {
  content: "";
  background-color: #f4f4f499;
  height: calc(100% - 10px);
  width: calc(100% + 10px);
  position: absolute;
  top: calc(50% - 10px);
  transform: translateY(-50%);
  left: 0;
  z-index: 0;
}
.StepProgress-item.subItem {
  margin-left: 40px;
}
.StepProgress-item.subItem.selected > * {
  z-index: 1;
}
.StepProgress-item.subItem.selected:after {
  content: "";
  left: -40px;
  width: calc(100% + 50px);
}
.StepProgress-item.subItem input[type="checkbox"]:checked {
  box-shadow: 0px 9px 17px -4px gray;
}
.StepProgress-item.subItem input[type="checkbox"]:checked + .StepProgress-item__InfoPassage:before {
  background-color: blue;
  left: -67px;
  width: 30px;
}
.StepProgress-item.subItem .StepProgress-item__InfoPassage {
  position: relative;
}
.StepProgress-item.subItem .StepProgress-item__InfoPassage:before {
  content: "";
  display: inline-block;
  position: absolute;
  left: -70px;
  top: 16px;
  width: 34px;
  height: 4px;
  transform: translateY(-50%);
  background-color: gray0;
  z-index: 1;
}
.StepProgress-item.subItem.is-active {
  position: relative;
}
.StepProgress-item.subItem.is-active:before {
  content: "";
  left: -40px;
  top: 14px;
  height: 100%;
}
.StepProgress-item.not-active * {
  color: #787878;
}
.StepProgress-item.not-active input[type="checkbox"]::before {
  width: 100%;
  height: 100%;
}
.StepProgress-item.not-active .StepProgress-item__InfoPassage + span {
  background-color: #f4f4f4;
}
.StepProgress-item:last-of-type {
  padding-bottom: 0;
}
.StepProgress-item.is-active:before {
  display: inline-block;
  content: "";
  position: absolute;
  top: 26px;
  height: calc(100% - 8px);
  width: 4px;
  background: blue;
  z-index: 2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<section class="FluxLibreStep2" id="step2">
                            <button class="FluxLibre__IconBackStep" id="returnToPreviousStep"></button>

                            <div class="FluxLibreStep2__top">
                                 

                                <div class="FluxLibreStep2__FromTo">
                                    <div class="FluxLibreStep2__From">
                                        <span>De</span>
                                        <div class="FluxLibreStep2__City">
                                            Paris </div>
                                    </div>
                                    <button type="button" class="FluxLibre__IconSwitch" id="fluxLibreSwitchCity"></button>
                                    <div class="FluxLibreStep2__To">
                                        <span>Vers</span>
                                        <div class="FluxLibreStep2__City">
                                            Caen</div>
                                    </div>
                                </div>
                            </div>


                            <div class="FluxLibreStep2__Road">
                                <ul class="StepProgress list-inline">
                                    <li class="StepProgress__FromToCity">
                                        Paris
                                    </li>
                                    <li class="StepProgress-item">
                                        <input type="checkbox" id="point1" name="passage" value="point1">
                                        <label for="point1" class="StepProgress-item__InfoPassage">
                                            Heudebouville
                                            <span class="infoORprice">
                                                12 €</span>
                                            <span class="icon icon-{{ iconPassage }}" aria-hidden="true">A10</span>
                                        </label>
                                    </li>
                                    <li class="StepProgress-item subItem">
                                        <input type="checkbox" id="point2" name="passage" value="point2">
                                        <label for="point2" class="StepProgress-item__InfoPassage">
                                            Montreuil
                                            <span class="StepProgress-item__subItemPassageNum">(n°6a)</span>
                                            <span class="infoORprice">
                                                12 €</span>
                                            <span class="icon icon-{{ iconSousPassage  }}" aria-hidden="true">A11</span>
                                        </label>
                                    </li>
                                     
                                    <li class="StepProgress-item">
                                        <input type="checkbox" id="point4" name="passage" value="point4">
                                        <label for="point4" class="StepProgress-item__InfoPassage">
                                            Babille
                                            <span class="infoORprice">
                                                12 €</span>

                                            <span class="icon icon-{{ iconPassage }}" aria-hidden="true">A12</span>
                                        </label>
                                    </li>
                                    <li class="StepProgress-item subItem not-active">
                                        <input disabled="disabled" type="checkbox" id="point5" name="passage" value="point5">
                                        <label for="point5" class="StepProgress-item__InfoPassage">
                                            Handover
                                            <span class="infoORprice">
                                                Ouverture du portic flux libre le 25/12/2024
                                            </span>
                                            <span class="icon icon-{{ iconSousPassage }}" aria-hidden="true">A13</span>
                                        </label>
                                    </li>
                                    <li class="StepProgress-item">
                                        <input type="checkbox" id="point6" name="passage" value="point6">
                                        <label for="point6" class="StepProgress-item__InfoPassage">
                                            Provide
                                            <span class="StepProgress-item__subItem-PassageNum">
                                                (n°6a)
                                            </span>
                                            <span class="infoORprice">
                                                6 €
                                            </span>
                                            <span class="icon icon-{{ iconPassage }}" aria-hidden="true">A51</label>
                                        </li>
                                    </li>
                                    <li class="StepProgress-item subItem">
                                        <input type="checkbox" id="point7" name="passage" value="point7">
                                        <label for="point7" class="StepProgress-item__InfoPassage">
                                             feedbackDo
                                            <span class="StepProgress-item__subItem-PassageNum">
                                                (n°6a)
                                            </span>
                                            <span class="infoORprice">
                                                6 €
                                            </span>
                                            <span class="icon icon-{{ iconPassage }}" aria-hidden="true">A51</label>
                                        </li>
                                    </li>
                                    <li class="StepProgress__FromToCity">
                                        Caen
                                    </li>
                                </ul>
                            </div>
                            <p>Selected option:
                                <span id="selectedOption"></span>
                            </p>

                            <div class="FluxLibreStep2__TotalPrice">
                                <p>{{ fluxLibreTotalPrice}}
                                </p>
                                <span id="totalPrice">0</span>
                            </div>
                        </section>


Solution

  • I used a selector to grab all the checked checkboxes. Then loop through the LIs adding the is-active class when we are between the first and last checked checkbox.

    var checkboxes = document.querySelectorAll(
        '.StepProgress-item input[type="checkbox"]'
    );
    
    checkboxes.forEach((checkbox, index) => {
      checkbox.addEventListener("change", function () {
        redrawSteps();
      });
    });
      
    const redrawSteps = () => {
      // remove existing blue path
      const bluePath = document.querySelectorAll('.StepProgress-item');
      bluePath.forEach(li => li.classList.remove("is-active"));
      // get all the checked checkboxes, so we can find the first and last
      const checked = document.querySelectorAll('.StepProgress-item input[type="checkbox"]:checked');
      
      if (checked.length > 1) {
        // get the first and last li that contain a checked checkbox
        const first = checked[0].closest("li");
        const last = checked[checked.length - 1].closest("li");
        let started = false;
        let finished = false;
        
        bluePath.forEach(li => {
          if (li === first) {
            started = true;
          } else if (li === last) {
            finished = true;
          }
    
          if (started && !finished) {
            li.classList.add("is-active");
          }
        });
      }
    };
    .StepProgress {
        position: relative;
        display: flex;
        flex-direction: column;
        list-style: none;
    
        &.inverse {
          flex-direction: column-reverse;
        }
        &:before {
          display: inline-block;
          content: "";
          position: absolute;
          top: 11px;
          left: 0;
          width: 10px;
          height: 100%;
          border-left: 4px solid #ccc;
        }
      }
      .StepProgress-item {
        position: relative;
        display: flex;
    
        input[type="checkbox"] {
          appearance: none;
          -webkit-appearance: none;
          width: 20px;
          height: 20px;
          border: 1px solid #ccc;
          border-radius: 50%;
          margin-right: 10px;
          background-color: $white;
          position: relative;
          top: 6px;
          left: -8px;
    
          &:checked {
            border: 1px solid  blue ;
    
            &:before {
              content: "";
              background-color: blue;
            }
          }
          &:before {
            content: "";
            display: block;
            width: 12px;
            height: 12px;
            background-color: #ccc;
            border-radius: 50%;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
          }
        }
    
        > label {
          font-size: 16px;
          font-weight: 700;
          padding-top: 5px;
    
          span {
            font-size: 14px;
            font-weight: 400;
          }
        }
        &.subItem {
          margin-left: 40px;
    
          input[type="checkbox"] {
            &:checked {
              & + label {
                &:before {
                  background-color: blue;
                }
              }
            }
          }
          > label {
            position: relative;
    
            &:before {
              content: "";
              display: inline-block;
              position: absolute;
              left: -70px;
              top: calc(50% + 2px);
              width: 30px;
              height: 4px;
              transform: translateY(-50%);
              background-color: gray;
            }
          }
       
          &.is-active::before {
            content: "";
            left: -40px;
            top: 15px;
            height: calc((100% + 45px) / 2);
            
          }
        }
      }
      .StepProgress-item:not(:last-child) {
        padding-bottom: 20px;
      }
      .StepProgress-item.is-active:last-of-type &,
      .StepProgress-item.is-active:first-of-type & {
        &::before {
          content: none;
        }
      }
      .StepProgress-item.is-active::before {
        display: inline-block;
        content: "";
        position: absolute;
        /* left: -1px; */
        top: 19px;
        height: calc((100% + 51px) / 2);
        width: 4px;
        background:  blue;
      }
    
      .StepProgress-item.is-done::before {
        border-left: 2px solid green;
      }
      .StepProgress-item.is-done::after {
        content: "✔";
        font-size: 10px;
        color: #fff;
        text-align: center;
        border: 2px solid green;
        background-color: green;
      }
    
      .StepProgress strong {
        display: block;
      }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <div class="FluxLibreStep2__Road">
    
                            <ul class="StepProgress list-inline">
                               <li class="StepProgress-item ">
                                  <input type="checkbox" id="point1" name="flavor" value="point1">
                                  <label for="point1">
                                     Point 1
                                     <span class="price"> 12 </span>
                                  </label>
                               </li>
                               <li class="StepProgress-item subItem ">
                                  <input type="checkbox" id="point2" name="flavor" value="point2">
                                  <label for="point2">
                                     Award an entry
                                     <span class="price"> 12 </span>
                                  </label>
                               </li>
                               <li class="StepProgress-item">
                                  <input type="checkbox" id="point3" name="flavor" value="point3">
                                  <label for="point3">
                                     Post a contest
                                     <span class="price"> 15 </span>
                                  </label>
                               </li>
                               <li class="StepProgress-item subItem">
                                  <input type="checkbox" id="point4" name="flavor" value="point4">
                                  <label for="point4">
                                     Handover
                                     <span class="price"> 22 </span>
                                  </label>
                               </li>
                               <li class="StepProgress-item">
                                  <input type="checkbox" id="point5" name="flavor" value="point5">
                                  <label for="point5">
                                     Provide feedback
                                     <span class="price"> 6 </span>
                                  </label>
                               </li>
                            </ul> 
    
                         </div>
                         <p>Selected option: <span id="selectedOption"></span></p>