Decrease progress bar on radio input value click

I have 2 separate code in one audit.php file and trying to combine it to decrease progress bar by the value of click on radio input I just can’t put the logic together The calculation should be (var total/$auditno * 100/100) The variable $auditno is above html code in <?php . If there is a better way any suggestion?

Function for decrease progress bar, this is where calculation (var total/$auditno * 100/100) should replace value=v1-10

function decrease() {
var v1=document.getElementById('p1').value;
document.getElementById("p1").value= v1 - 10;
}

Function to calculate value of click radio input

function setRadios() {
        function sumRadios() {  
        var total = 0, i = 1, oForm = this.form;
        while (radgrp = oForm.elements['Set' + (i++)])
        {
            j = radgrp.length;
            do
                if (radgrp[--j].checked)
                {
                    total += Number(radgrp[j].value);
                    break;
                }
            while (j);
        }
                oForm.elements.total.value = total.toFixed(2);
    }
var i = 0, input, inputs = document.getElementById('myemailform').getElementsByTagName('input');
while (input = inputs.item(i++))
    if (input.name.match(/^Set\d+$/))
        input.onclick = sumRadios;
}

onload = setRadios;

and Html

<div id="wrapper">
<form id="myemailform">
<fieldset class="audit">
<legend>Set 1</legend>
<input id="r1" type="radio" name="Set1" value="0" /><label for="r1">0</label><br />
<input id="r2" type="radio" name="Set1" value="1" /><label for="r2">1</label><br />
<input id="r3" type="radio" name="Set1" value="2" /><label for="r3">2</label>
</fieldset>

<fieldset class="audit">
<legend>Set 2</legend>
<input id="r4" type="radio" name="Set2" value="0" /><label for="r4">0</label><br />
<input id="r5" type="radio" name="Set2" value="1" /><label for="r5">1</label><br />
<input id="r6" type="radio" name="Set2" value="2" /><label for="r6">2</label>
</fieldset>

<fieldset class="audit">
<legend>Set 3</legend>
<input id="r7" type="radio" name="Set3" value="0" /><label for="r7">0</label><br />
<input id="r8" type="radio" name="Set3" value="1" /><label for="r8">1</label><br />
<input id="r9" type="radio" name="Set3" value="2" /><label for="r9">2</label>
</fieldset>

<fieldset style="position:relative;top:36px;width:140px;">
<input id="total" type="text" name="total" value="" /><strong> total</strong>&nbsp;&nbsp;
<input type="reset" style="font-size:11px;" />
</fieldset>
</form>
</div><br><br>

<progress value="100" max="100" id=p1>100%</progress>
<input type=button value='Decrease' onClick='decrease();'>
<progress value="100" max="100" id=p1>100%</progress>

should be:

<progress value="100" max="100" id="p1">100%</progress>

Thanx, I have rectified code but I still need to know how to decrease progress bar

Here’s my approach (with progress bar increasing):

<!DOCTYPE html>
<html id="html">
<head>
<meta charset="UTF-8">
<title>Progress Bar</title>
</head>
<body>
<div id="wrapper">
<form id="myemailform">
<fieldset class="audit">
<legend>Set 1</legend>
<input id="r1" type="radio" name="Set1" value="0" /><label for="r1">0</label><br />
<input id="r2" type="radio" name="Set1" value="1" /><label for="r2">1</label><br />
<input id="r3" type="radio" name="Set1" value="2" /><label for="r3">2</label>
</fieldset>

<fieldset class="audit">
<legend>Set 2</legend>
<input id="r4" type="radio" name="Set2" value="0" /><label for="r4">0</label><br />
<input id="r5" type="radio" name="Set2" value="1" /><label for="r5">1</label><br />
<input id="r6" type="radio" name="Set2" value="2" /><label for="r6">2</label>
</fieldset>

<fieldset class="audit">
<legend>Set 3</legend>
<input id="r7" type="radio" name="Set3" value="0" /><label for="r7">0</label><br />
<input id="r8" type="radio" name="Set3" value="1" /><label for="r8">1</label><br />
<input id="r9" type="radio" name="Set3" value="2" /><label for="r9">2</label>
</fieldset>

<fieldset style="position:relative;top:36px;width:140px;">
<input id="total" type="text" name="total" value="" /><strong> total</strong>&nbsp;&nbsp;
<input type="reset" style="font-size:11px;" />
</fieldset>

</form>
</div>
<br><br>
<progress value="0" max="100" id="p1">0</progress>
</body>
<script>
var radios=document.getElementsByTagName("input");	// array

// add event listeners on load
for(let z=0 ; z<radios.length ; z++){
  radios[z].addEventListener("click",showProgress);
}

function showProgress() {
  let sum=0;
  for(let z=0 ; z<radios.length ; z++){
    if(radios[z].checked) sum++;
  }
  document.getElementById("p1").value = sum * 100/3;
}
</script>
</html>

(I have deleted the “Decrease” button)

Note I am summing the number of radio buttons that are checked. I am not summing the number of clicks.

Postulation #1:
Can resolve the last while as a forEach.

Postulation #2:
The walking of the arrays can be more simply resolved as checking the value of the radio rather than walking each individual node.

Postulation #3:

is redundant, as multiplying a Number by 100 and then dividing it by 100 yields the same number. If the intention is to make the number a 2-decimal value, run it through toFixed, as you have done elsewhere.

Postulation #4: No value for auditno is overly available to the code.

Postulation #5: total is defined in the narrow scope of sumRadios, and is not available outside of that function.

Thanx working 100% I just need it to decrease from 100% In other words the progress bar will be 100% and on click it will decrease

So revert the <progress> element to how you had it and change the calculation to:
document.getElementById("p1").value = 100 - sum*100/3;

That seems the wrong way round to me :grinning:.

BTW: For the last fieldset I suggest you use
margin-top:36px;
instead of
position:relative;top36px;

Thanx working now as I want it to work.
I know its sound odd to decrease progress bar but in my instance I start a safety audit on 100% as I assume that the company safety is 100% and then for each question failed the progress bar reduces. It is normally on worst case scenario about 30% So its easier to reduce as all radio buttons first input is checked which has a zero and does not influence the progress bar. Should actually called it a regress bar but sound stupid

And thanx for the tip “margin-top”

Note if you change the number of radio button groups you will need to change the divide by 3 in that final calculation.

That’s a good idea about calling it a regress bar. I’ve renamed the progress bar to regression, and have it starting at 100 so that it regresses down to 0.

<progress value="100" max="100" id="regression">100%</progress>
  document.getElementById("regression").value = 100 - sum * 100 / 3;

There are some other things that I would improve on here too.

Update the progress content with its value

The innerHTML of the progress element should be updated along with the value, so that it works on interfaces that don’t know how to show the progress bar.

  document.getElementById("regression").value = 100 - sum * 100 / 3;
  document.getElementById("regression").innerHTML = 100 - sum * 100 / 3 + "%";

To avoid duplication of the calculation, I’ll put the calculation into a value called regressionAmount

  const regressionAmount = 100 - sum * 100 / 3;
  document.getElementById("regression").value = regressionAmount;
  document.getElementById("regression").innerHTML = regressionAmount.toFixed(2) + "%";

And the access to the regression element is also being repeated there, so I’ll help to shorten the lines by putting that into a regression variable too.

  const regression = document.getElementById("regression");
  const regressionAmount = 100 - sum * 100 / 3;
  regression.value = regressionAmount;
  regression.innerHTML = regressionAmount.toFixed(2) + "%";

The .toFixed(2) is so that the visual display doesn’t end up as 66.666666666666% and is instead rounded to the second decimal place, showing 66.67%

Counting the radio sets

Getting slightly more complicated, we can get a unique list of radio sets. That way when you add more sets the code will continue to work without needing to be updated.

The radios list contains some elements that aren’t radios, so let’s fix that first.

// var radios=document.getElementsByTagName("input");
var radios = document.querySelectorAll("[type=radio]");

We can now map that list of radios to their names, and use new Set to remove duplicates from that list letting us easily get a count of the unique radio names.

  const allRadioNames = Array.from(radios).map((input) => input.name);
  const radioSetsCount = new Set(allRadioNames).size;
  const regressionAmount = 100 - sum * 100 / radioSetsCount;

Replace for/loop code with Array processing methods

Seeing the let z in the code is an opportunity to improve on that too. You are wanting a count of checked radios. The z variable of the loop is an unnecessary distraction that’s not needed. Instead, we can get the number of checked items by using the filter method.

  // let sum=0;
  // for(let z=0 ; z<radios.length ; z++){
  //   if(radios[z].checked) sum++;
  // }
  const sum = radios.filter((radio) => radio.checked).length;

Use Array processing to assign click event

As I’m dealing with the for loops, another improvement that I make is to the event assignment.

// for(let z=0 ; z<radios.length ; z++){
//   radios[z].addEventListener("click",showProgress);
// }
radios.forEach((radio) => radio.addEventListener("click", showProgress));

The updated code

That leaves us with the following code:

var radios = Array.from(
  document.querySelectorAll("[type=radio]")
);

radios.forEach((radio) => radio.addEventListener("click", showProgress));

function showProgress() {
  const sum = radios.filter((radio) => radio.checked).length;
  const allRadioNames = Array.from(radios).map(
    (input) => input.type === "radio" && input.name
  );
  const radioSetsCount = new Set(allRadioNames).size;
  const regressionAmount = 100 - sum * 100 / radioSetsCount;
  const regression = document.getElementById("regression");
  regression.value = regressionAmount;
  regression.innerHTML = regressionAmount.toFixed(2) + "%";
}

Extract code from the showProgress function

Here now is where I normally split up the showProgress function into two functions, one to calculate the percentage and another to update the percentage.

function showProgress() {
  function countRadioSets(radio) {
    const allRadioNames = Array.from(radios).map(
      (input) => input.type === "radio" && input.name
    );
    return radioSetsCount = new Set(allRadioNames).size;
  }
  function countCheckedRadioSets(radio) {
    return radios.filter((radio) => radio.checked).length;
  }
  function calculateRegression(radios) {
    const sum = countCheckedRadioSets(radios);
    const setsCount = countRadioSets(radios);
    return 100 - sum * 100 / setsCount;
  }
  function updateRegression(regressionBar, amount) {
    regressionBar.value = amount;
    regressionBar.innerHTML = amount.toFixed(2) + "%";
  }
  const regressionBar = document.getElementById("regression");
  const regressionAmount = calculateRegression(radios);
  updateRegression(regressionBar, regressionAmount);
}

The working code is found at https://jsfiddle.net/mgujk4es/

1 Like

Thanx Paul this is working the way I need it to work and thanx for explanation help me to understand the logic better. Oops another problem if I click on 0 (zero) it shouldn’t reduce the regression bar because the value = zero but now it does reduce the regression bar but it should work as follow

If I select 1 or 2 the regression bar must reduce and if I click 0 it must reset

On another note how can I add the percentage it drop on top of the regression bar and I want to submit the percentage via form to another.php file

That’s easy to achieve, especially now that we’ve extracted several useful functions from the code. Here is the code that counts the radios:

  function countCheckedRadioSets(radio) {
    return radios.filter((radio) => radio.checked).length;
  }

I want to have another filter in there for aboveZero radios, so let’s make some room and move that filter condition out to a separate variable:

  function countCheckedRadioSets(radio) {
    const isChecked = (radio) => radio.checked;
    return radios.filter(isChecked).length;
  }

We can now add another one to join isChecked, that tests if the value is above zero.

  function countCheckedRadioSets(radios) {
    const isChecked = (radio) => radio.checked;
    const isAboveZero = (radio) => radio.value > "0";
    return radios.filter(isChecked).filter(isAboveZero).length;
  }

The function name about counting checked is not really true now. Instead it’s counting the regressions, so let’s rename that function to have a more suitable name.

  // function countCheckedRadioSets(radios) {
  function countRegressions(radios) {
...
    // const sum = countCheckedRadioSets(radios);
    const sum = countRegressions(radios);

The working code for that can be found at https://jsfiddle.net/ov8uj0zr/

Let’s take a look at the calculateRegression function:

  function calculateRegression(radios) {
    const sum = countRegressions(radios);
    const setsCount = countRadioSets(radios);
    return 100 - sum * 100 / setsCount;
  }

As zero regression results in a progress of 100%, we should move that 100 minus part out of the regression function.

    // return 100 - sum * 100 / setsCount;/
    return sum * 100 / setsCount;
...
  // updateRegression(regressionBar, regressionAmount);
  updateRegression(regressionBar, 100 - regressionAmount);

We can now display that regressionAmount value directly in the total section too.

  function updateTotal(totalField, amount) {
    totalField.value = amount.toFixed(2) + "%";
  }
...
  const totalField = document.getElementById("total");
...
  updateTotal(totalField, regressionAmount);
  updateRegression(regressionBar, 100 - regressionAmount);

The updated code can be found at https://jsfiddle.net/ov8uj0zr/1/

You should use the action attribute on the HTML form to do that.

<form id="myemailform" action="reportRegression.php">

A submit button in the form lets you submit the form to that php file.

<input type="submit">

Thanx Thanx everything is working sending var via hidden input in form is now also working just need one last help with displaying the value in progress bar

The calculation to display will be 100 - total I try to display with following code but not sure how to get the data-label value right

<div id="div2"><progress value="100" max="100" id="regression" data-label=total></progress>

I have add to my css the following style to display data label

progress {
  width: 100%;
  display: block; /* default: inline-block */
  margin: 2em auto;
  padding: 3px;
  border: 0 none;
  background: #444;
  border-radius: 14px;
  margin-bottom: -2px;
}
progress::-moz-progress-bar {
  border-radius: 12px;
  background: orange;

}
/* webkit */
@media screen and (-webkit-min-device-pixel-ratio:0) {
  progress {
    height: 25px;
  }
}
progress::-webkit-progress-bar {
    background: transparent;
}  
progress::-webkit-progress-value {  
  border-radius: 12px;
  background: orange;
} 

progress:before {
  content: attr(data-label);
  font-size: 1.0em;
  vertical-align: 0;
  font-weight: bold;
  
  /*Position text over the progress bar */
  position:absolute;
}

It’s in the updateRegression function that is a good place for the data-label is updated. Here’s what that function currently looks like:

  function updateRegression(regressionBar, amount) {
    regressionBar.value = amount;
    regressionBar.innerHTML = amount.toFixed(2) + "%";
  }

The dataset API is a preferred way to manage things.

  function updateRegression(regressionBar, amount) {
    regressionBar.value = amount;
    const roundedAmount = amount.toFixed(2) + "%";
    regressionBar.innerHTML = roundedAmount;
    regressionBar.dataset.label = roundedAmount;
  }

When it comes to values, it’s preferred to internally keep the full decimal value, but to round the figure for visual display to the screen.

Here is the updated code: https://jsfiddle.net/gL02f6xr/

Thanx Paul for the learning curve. Highly appreciated, as always you are the one that help me on many of my failed projects.

Everything is working now 100%, I only changed the data label from total to 100%

1 Like

Any Idea why the data-label is not displaying on iphone (ios)

<progress value="100" max="100" id="regression" data-label=100%></progress>

css

progress:before {
  content: attr(data-label);
  font-size: 1.1em;
  vertical-align: 0;
  font-weight: bold;
  z-index: 999999;
  margin-left: -20px;
  /*Position text over the progress bar */
  position:absolute;
}

It is working on all browsers except on safari - iphone - ios

I believe the progress element is classed as a replaced element (like inputs and images) and therefore the use of :before and :after is undefined in the specs. Some browsers will and some browsers won’t.

I believe the semantic solution is to use the output element to show a value.

1 Like