Radio selection and Price updating

 <div class="radio-price">
 	<div class="fchild-1">
			<label for="fraise1" class="fundariser-radio">
			<input type="radio" id="fraise1" name="fraise[]" class="fundariser-radio__input" value="79" data-currency="$">
			<input type="text" value="79">
 	</div>
 	<div class="fchild-1">
			<label for="fraise1" class="fundariser-radio">
			<input type="radio" id="fraise1" name="fraise[]" class="fundariser-radio__input" value="79" data-currency="$">
			<input type="text" value="79">
 	</div>
 	<div class="fchild-1">
			<label for="fraise1" class="fundariser-radio">
			<input type="radio" id="fraise1" name="fraise[]" class="fundariser-radio__input" value="79" data-currency="$">
			<input type="text" value="79">
 	</div>
 	<div class="fchild-1">
			<label for="fraise1" class="fundariser-radio">
			<input type="radio" id="fraise1" name="fraise[]" class="fundariser-radio__input" value="79" data-currency="$">
			<input type="text" value="79">
 	</div>
 </div>
</div>

The majority of discussion I will be posting regarding JS, but some preliminary questions are related to HTML

someone build this for me 4 years back when I didn’t know anything about HTML/CSS.
I am creating this w/o seeing how that was done.

  1. Is the structure of the HTML I posted is semantically ok?
  2. What parts in the input are redundant and do not need any usage? id, for, or any other input attribute?
  3. How should we take the default value? value="79" or in another way such as default-value=“79”

The f-child1 is not needed and should go.
The unique identifiers aren’t unique and must be changed to ones that are unique, along with the label’s for attribute.

Otherwise, it seems good to go.

1 Like

Oh, and the fundraiser-radio and fundraiser-radio__input classes seem a waste too, and don’t need to be there either.

A CSS selector for the radio input for example, is .radio-price [type=radio] instead.

1 Like

Thanks for replying, I hope you were not affected by the COVID threat and were taking good care of yourselves and your loved ones.

I have revised the HTML sir, here I go →

<div class="radio-price">
    <div>
        <label for="fraise1">
            <input type="radio" id="fraise1" name="fraise[]" value="79" data-currency="$">
            <input type="text" value="79">
    </div>
    <div>
        <label for="fraise2">
            <input type="radio" id="fraise2" name="fraise[]" value="79" data-currency="$">
            <input type="text" value="79">
    </div>
    <div>
        <label for="fraise3">
            <input type="radio" id="fraise3" name="fraise[]" value="79" data-currency="$">
            <input type="text" value="79">
    </div>
    <div>
        <label for="fraise4">
            <input type="radio" id="fraise4" name="fraise[]" value="79" data-currency="$">
            <input type="text" value="79">
    </div>
</div>

I hope it is good to go.

1 Like

I came up with this code:

const input = document.querySelector('input').setAttribute("type", "text");
const price = document.getElementsByClassName('price');
input.addEventListener('change', changeValue);
function changeValue(evt) {
	price.innerHTML = evt.target.value;
}

But it is yet not binded with the radio button click? How can I bind it with two events radio button + input text?

As there are multiple radios, you will want to loop through each radio, adding an event listener to them.

const prices = document.querySelector(".radio-price");
const priceRadios = prices.querySelectorAll("[type=radio]");
priceRadios.forEach(function (radio) {
    radio.addEventListener("change", updatePriceHandler);
});

We usually use a handler function, so that it can retrieve the event information that’s useful, and pass it on to something else.

In the handler, we get the radio, and pass it to updatePrice.

function priceRadioHandler(evt) {
    const radio = evt.target;
    updatePrice(radio);
}

The updatePrice function is nice and easy to achieve:

function getInputFromRadio(radio) {
    return radio.nextElementSibling;
}
function updatePrice(radio) {
    const input = getInputFromRadio(radio);
    document.querySelector("#price").innerHTML = input.value;
}

That works for when clicking on the radio buttons.

Next, we want an update to the inputs to update the price too. We can use the same getInputFromRadio function that we used before.

const priceInputs = Array.from(priceRadios).map(getInputFromRadio);
priceInputs.forEach(function (input) {
    input.addEventListener("change", priceInputHandler);
});

The priceInputHandler gets the input element from the event information, and uses that to get the radio and pass it on to the updatePrice function.

function priceInputHandler(evt) {
    const input = evt.target;
    const radio = getRadioFromInput(input);
    updatePrice(radio);
}

There is a problem though. You can select radio 2, and then update input 3 and the price updates to that third one. We don’t want that. Instead we want to search through for the selected radio element, and show only that price.

That means that the updatePrice function shouldn’t just get the input value. Instead it needs to use the radio element to get the selected radio button, and only show the value of that one.

function getCurrentRadio(radio) {
    const form = radio.form;
    const radios = form.elements[radio.name];
    return Array.from(radios).find(function (radio) {
        return radio.checked;
    });
}
function updatePrice(radio) {
    const currentRadio = getCurrentRadio(radio);
    const input = getInputFromRadio(currentRadio);
    document.querySelector("#price").innerHTML = input.value;
}

There’s an example page with the code in action at https://jsfiddle.net/owrg1bsa/

Try selecting a radio button, then a price input from a different line and see if that lines up with your expected behaviour.

To help avoid confusion, I’ve added a bit more so that when you click on a price field, it selects that radio too.

function priceClickHandler(evt) {
    const input = evt.target;
    const radio = getRadioFromInput(input);
    radio.checked = true;
}
...
const priceInputs = Array.from(priceRadios).map(getInputFromRadio);
priceInputs.forEach(function (input) {
    input.addEventListener("click", priceClickHandler);
    input.addEventListener("change", priceInputHandler);
});

See if you like that variation better, at https://jsfiddle.net/owrg1bsa/1/

2 Likes
function getCurrentRadio(radio) {
    const form = radio.form;
    const radios = form.elements[radio.name];
    return Array.from(radios).find(function (radio) {
        return radio.checked;
    });
}

const radios = form.elements[radio.name]; → This will give name="fraise[]"
Array.from(radios) → This will fetch individual letters/numerical and convert them into an array.
I get stuck at this point what are we trying to achieve on/after this point and hos this part of the code is connecting the dots of our objective?

The objective is to find the selected radio button. That one will have a checked status that is true. The find method is very good at searching through all of the radio buttons to give us that checked radio button, which is the selected radio button.

That find method only works on arrays, so we turn the list of radio buttons into an array of radio buttons, so that we can use the find method to give us currently selected radio button.

1 Like

Not quite.

Instead it gives an array-like collection of all the radio button elements that have that name.

Officially that array-like collection is called an HTMLFormControlsCollection but it doesn’t have much in the way of useful methods available to use on it.

That’s why we convert it into an array, so that we can use the wide range of array-handling methods on the list of radio buttons.

1 Like

A technique that gives a more useful list is to use querySelectorAll instead to get the radio buttons:

const radios = form.querySelectorAll(`[name=${radio.name}]`);

That gives us a static nodeList of those radio buttons, where static means that the list doesn’t change when the DOM changes.

That nodeList has the forEach method available to it, but we would need to convert it to an array before using fancier methods such as find, filter, map, reduce, etc.

Because of that, it’s better to not confuse things with querySelectorAll and make it very clear what is happening by using form.elements instead.

1 Like

How can we print ths array in console:
fraise[]
I tried, but it didnt worked.

We can use console.log to show the radios collection in the console, and the array version of it.

function getCurrentRadio(radio) {
    const form = radio.form;
    const radios = form.elements[radio.name];
    console.log(radios);
    console.log(Array.from(radios));
    return Array.from(radios).find(function (radio) {
        return radio.checked;
    });
}

Opening up the __proto__ section shows us that the radios collection has very few methods available to it, compared with the __proto__ section of the array version.

1 Like

Showing the fraise[] elements in the console is tricky because the [] symbols don’t make a valid selector. There are a few ways to deal with that.

One way is to escape each of the square brackets:

const fraiseElements = document.querySelectorAll("[name=fraise\\[\\]]");
console.log(fraiseElements);

But that gets tricky to read and understand.

Another way that I prefer is to use ^= which is a prefix selector instead.

const fraiseElements = document.querySelectorAll("[name^=fraise]");
console.log(fraiseElements);
1 Like

You have used $ are we in the realms of JQuery now?

No jQuery is involved. Backticks are used to denote a template literal. Inside of a template literal you can use ${variableName} to insert the contents of that variable name.

That way it provides more of a natural flow than when compared with standard strings.

// const radios = form.querySelectorAll("[name=" + radio.name + "]");
const radios = form.querySelectorAll(`[name=${radio.name}]`);
1 Like

This was very explaining. Difficulty in coding is encountered when we could not visualize what is happening around us. This helped a lot. Thanks for the direction and insight.

console.log(radios);
console.log(Array.from(radios));

This array.from is quite complicated and has a very vast usage.

1 Like