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.
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>
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>