When I have this form
<form id="frm">
<input type="radio" name="test" value="a">
</form>
calling frm.test.value = "b"
gets the input Node and changes the attribute value. On the other hand in this form
<form id="frm">
<input type="radio" name="test" value="a">
<input type="radio" name="test" value="b">
</form>
calling frm.test.value = "b"
gets RadioNodeList object and checks the radiobutton with value "b" (if any).
Is it possible to obtain RadioNodeList object with length less than 2 to safely call myobj.value = "b"
to check radiobutton (if any with such value) and not change its value attribute?
I don't look for any workaround like checking the type or some silly recipes like
const checkRadioValue = (frm, name, value) => frm &&
frm.querySelectorAll(`[name=${name}]`).forEach(item =>
item.checked = item.value==value
)
I want to safely obtain RadioNodeList (or equivalent live collection) from the DOM without testing how many radiobuttons are in the group.
CodePudding user response:
A RadioNodeList
does not get returned if there is only one radio button in the "group". It is an interface that encompasses a NodeList
of <inputs>
of type radio
.
Note: You will not be able to manually encapsulate a single <input type="radio">
inside a RadioNodeList
, because it is not instantiable. You will need to add a check, as describe below, to safely set the value of a single radio.
In order to "safely" check a radio button value, you need to check if it is a RadioNodeList
or an <input type="checkbox">
element. If it is a RadioNodeList
simply attempt to set the value. But if it is a single element, you will need to compare the input value and then set checked
to true
.
const isRadio = (input) =>
input && input.tagName === 'INPUT' && input.type === 'radio';
const setRadioValue = (radio, value) => {
if (radio instanceof RadioNodeList) {
radio.value = value; // Attempt to find a valid value
} else if (isRadio(radio) && radio.value === value) {
radio.checked = true;
}
};
// Will not change value
setRadioValue(document.forms['frm-fail-1'].elements.test, 'c');
setRadioValue(document.forms['frm-fail-2'].elements.test, 'b');
// Will change value
setRadioValue(document.forms['frm-pass-1'].elements.test, 'b');
setRadioValue(document.forms['frm-pass-2'].elements.test, 'a');
* { margin: 0; padding: 0; }
*, *:before, *:after { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
body { display: grid; grid-template-columns: repeat(2, 1fr); }
body > div { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.5em; }
form { display: flex; border: thin dashed red; padding: 0.25em; gap: 0.25em; }
<div>
<h2>Fail</h2>
<form id="frm-fail-1">
<input type="radio" name="test" value="a">
<input type="radio" name="test" value="b">
</form>
<form id="frm-fail-2">
<input type="radio" name="test" value="a">
</form>
</div>
<div>
<h2>Pass</h2>
<form id="frm-pass-1">
<input type="radio" name="test" value="a">
<input type="radio" name="test" value="b">
</form>
<form id="frm-pass-2">
<input type="radio" name="test" value="a">
</form>
</div>
CodePudding user response:
The behaviour of namedItem(name)
is described here as
- If name is the empty string, return null and stop the algorithm.
- If, at the time the method is called, there is exactly one node in the collection that has either an id attribute or a name attribute equal to name, then return that node and stop the algorithm.
- Otherwise, if there are no nodes in the collection that have either an id attribute or a name attribute equal to name, then return null and stop the algorithm.
- Otherwise, create a new RadioNodeList object representing a live view of the HTMLFormControlsCollection object, further filtered so that the only nodes in the RadioNodeList object are those that have either an id attribute or a name attribute equal to name. The nodes in the RadioNodeList object must be sorted in tree order.
- Return that RadioNodeList object.
I haven't found any other mention about RadioNodeList in the specs, so it seems that the only way to consistently obtain RadioNodeList is to add dummy radios, call the namedItem and remove the dummy radios, since RadioNodeList is a live collection.
HTMLFormElement.prototype.getRadioNodeList = function(name) {
var result = this.elements[name];
if(result instanceof RadioNodeList) return result;
var dummy0;
if(!result) {
dummy0 = document.createElement("input");
dummy0.type = "radio";
dummy0.name = name;
this.appendChild(dummy0);
}
var dummy1 = (dummy0||result).cloneNode();
this.appendChild(dummy1);
result = this.elements[name];
dummy0?.remove();
dummy1.remove();
return result;
}
This is bad, of course, to extend objects we do not own, but at least a proposal to call in the client code
myform.getRadioNodeList("test").value = "a";
To check the radiobutton with value "a" (if any) of radiobutton group named "test" regardless of the number of items the group contains.