How to organize radio buttons in separate lines?

Hello,

I’m currently working on radio buttons.
Right now, I have the following code.

gender = createRadio();
gender.option('male');
gender.option('female');
gender.option('prefer not to say');
gender.position(10, 30);

This gives me the following results.
11

However, I want the radio buttons to appear in separate lines.
I’ve tried working on using gender.position() and CSS but haven’t been successful.

Looking at the generated HTML, it would be so much easier if I could insert ul /ul li /li elements to the individual options of the radio button, but I haven’t figured out how to do this.

<div style="position: absolute; left: 10px; top: 30px;">
<input type="radio" value="male" name="defaultradio0" id="defaultradio0-0">
<label for="defaultradio0-0">male</label>

<input type="radio" value="female" name="defaultradio0" id="defaultradio0-1">
<label for="defaultradio0-1">female</label>

<input type="radio" value="prefer not to say" name="defaultradio0" id="defaultradio0-2">
<label for="defaultradio0-2">prefer not to say</label></div>

Would somebody be kind enough to tell me if there is a way to do this?

Thank you.

1 Like

yes, very difficult when the text strings are very different in length,
https://editor.p5js.org/kll/sketches/4QNj9OrZ9

1 Like

Hi @togo, You can also use select option , it is a good option and you doesn’t have care about the positions.

`

1 Like

When we call [u]p5[/u]::createRadio(), 1st it merely calls [u]p5[/u]::createDiv() internally and returns that for us:

It’s only when we call its option() method that it finally creates an <input type="radio">:

And its corresponding <label for="radio's id">:

And then returns the created <input type="radio"> for us:

In short, all <input type="radio"> + <label for="radio's id"> pairs are wrapped up in 1 <div>.

The problem w/ that is that each <input type="radio"> + <label for="radio's id"> pair tries to accommodate themselves horizontally within their sole <div>'s available width.

In order to force each pair to occupy its own row we’d need to create a <div> container for each pair.

For that, I’ve made a function which does just that:

function encloseEachInputLabelPairIntoASubDiv(radioDivElement) {
  const inputs = selectAll('input', radioDivElement),
        labels = selectAll('label', radioDivElement),
        len = inputs.length;

  for (let i = 0; i < len; ++i)
    createDiv().parent(radioDivElement).child(inputs[i]).child(labels[i]);
}

It grabs all <input> & <label> tag elements within the radio’s <div> via p5::selectAll():

And then loop over them as a pair each iteration, creating a <div> for each of them via p5::createDiv():

And making each of those new <div> elements a child of the radio’s parent <div> via p5.Element::parent():

Finally transferring the <input> & <label> pair into the new <div> via p5.Element::child():

1 Like

It seems like all’s solved. But encloseEachInputLabelPairIntoASubDiv() introduces a serious bug! :bug:

Both methods selected() & value() stop working after encloseEachInputLabelPairIntoASubDiv()! :face_with_head_bandage:

Both of them depend on a “hidden” method called _getInputChildrenArray(): :secret:



The problem is that _getInputChildrenArray() searches for <input> elements at radio’s <div> 1st inner level only.

However, after encloseEachInputLabelPairIntoASubDiv(), the 1st inner level got inner <div> containers only.

All <input> & <label> elements are now inside those inner <div> containers.

In order to fix the bug we’ve introduced, we need to replace _getInputChildrenArray()'s current search code w/ a smarter 1 that recursively searches all descendants of the radio’s parent element: :mage:

function fixRadioDivElement(radioDivP5Element) {
  radioDivP5Element._getInputChildrenArray = function () {
    return this.elt.getElementsByTagName('input');
  }
}

Here’s the whole radio button sketch. You can see it online too on the link below: :sunglasses:

/**
 * Create Vertical Radio Buttons (v1.0.1)
 * GoToLoop (2019-Apr-07)
 *
 * https://Discourse.Processing.org/t/
 * how-to-organize-radio-buttons-in-separate-lines/10041/5
 *
 * https://Bl.ocks.org/GoSubRoutine/5b18d019959031f517fc218667faa688
 */

"use strict";

let radio;

function setup() {
  createCanvas(150, 100);
  noLoop();

  radio = createRadio().position(20, 25).changed(redraw);

  radio.option('male', color('cyan'));
  radio.option('female', color('pink')).checked = true;
  radio.option('prefer not to say', color('orange'));

  encloseEachInputLabelPairIntoASubDiv(radio);
  fixRadioDivElement(radio);
}

function draw() {
  background(radio.value());
}

function encloseEachInputLabelPairIntoASubDiv(radioDivElement) {
  const inputs = selectAll('input', radioDivElement),
        labels = selectAll('label', radioDivElement),
        len = inputs.length;

  for (let i = 0; i < len; ++i)
    createDiv().parent(radioDivElement).child(inputs[i]).child(labels[i]);
}

function fixRadioDivElement(radioDivP5Element) {
  radioDivP5Element._getInputChildrenArray = function () {
    return this.elt.getElementsByTagName('input');
  }
}
2 Likes

To kll:
That’s really a good hack! I never thought of it this way! Thank you for this piece of wisdom.

To bhaaskar:
I think this is also a good workaround! Thank you for offering your wisdom!

To GoToLoop:
Thank you so so much for this deep knowledge.

In short, all <input type="radio"> + <label for="radio's id"> pairs are wrapped up in 1 <div> .

Yes, you’re absolutely right. I observed this after looking at the generated HTML and I was wondering a way to overcome this situation. By drawing examples from the original source code of p5.js, you have brilliantly explained what is going behind p5.js. Also, thank you for providing the function. This has enabled me to deepen my understanding of p5.js.

2 Likes

Another way is through the css file, ie:

label::after{
  content: '\a';
  white-space: pre;
}

(the \a character is the <br> char).

Not sure if it introduces other issues, but @hspencer 's css tip works perfectly in my case. Thank you!