1

I am trying to re-create the functionality of https://chriscoyier.net/ where you have a form with radio buttons, and as you click a radio button, the text changes.

What I started with was:

window.onload=function() {

  document.getElementById("hidden_elements").style.display="none";

  //  attach the click event handler to the radio buttons
  var radios = document.forms[0].elements["group1"];
  for (var i = [0]; i < radios.length; i++)
    radios[i].onclick=radioClicked;
}

function radioClicked() {
   if (this.value == "two") {
    document.getElementById("hidden_elements").style.display="block";
   } else {
    document.getElementById("hidden_elements").style.display="none";
   }
}
<form id="picker" method="post" action="">
Item 1: <input type="radio" name="group1" value="one" />
Item 2: <input type="radio" name="group1" value="two" />
Item 3: <input type="radio" name="group1" value="three" /><br />
<br />
<div id="hidden_elements">
Input 1: <input type="text" id="intext" />
Input 2: <input type="text" id="intext2"  />
Input 3: <input type="text" id="intext3"  /><br /><br />
</div>
<input type="submit" id="submitbutton" value="Send form" />
</form>

This allows me hide or display the div with id="hidden_elements" if input number 2 is selected.

What I want to do is hide or display the individual elements of "hidden_elements" based on the input 1, 2 or 3.

I tried changing the "hidden_elements" attributes to:

<div id="hidden_elements">
Input 1: <input type="text" name="one" />
Input 2: <input type="text" name="two"  />
Input 3: <input type="text" name="three"  /><br /><br />
</div>

and JS to:

var hide = document.getElementById("hidden_elements");
        for (var i = [0]; i < hide.length; i++)
            hide[i].style.display = "none";

function radioClicked() {
        document.getElementsByName(this.value).style.display = "block"
    }

But that doesn't work either.

I've tried a less appealing approach using if/else statements, but that too doesn't work.

<div id="paragraphs">
    <div id="hidden_element" name="one">Paragraph 1 </div>
    <div id="hidden_element" name="two">Paragraph 2 </div>
    <div id="hidden_element" name="three">Paragraph 3 </div>
</div>

function radioClicked() {
        if (this.value == "one") {
            document.getElementById("hidden_element_one").style.display="block";
        } else if (this.value == "two") {
            document.getElementById("hidden_element_two").style.display="block";
        } else if (this.value == "three") {
            document.getElementById("hidden_element_three").style.display="block";
        }
    }

I've tried a few more approaches but the behaviour isn't what I want. Any idea how I can change the display from "none" to "block" based on the selected radio-button? (I know you can do it with JQuery but I'm trying to learn Javascript)

ron_g
  • 1,474
  • 2
  • 21
  • 39
  • 1
    first of all, never use same ID for different elements (like last example), Id's MUST be unique. in the last example change every id to it's correspondent number (ex.: hidden_element_one), then try again – Calvin Nunes Jan 05 '18 at 11:30
  • What should `var i = [0]` do?? – Jonas Wilms Jan 05 '18 at 12:17
  • @CalvinNunes - you're right. The reason they're all the same ID's is because I didn't roll-back everything I was doing. I had changed the IDs of each to "hidden_element_one", etc., and in the JS function I was trying to use getElementById("hidden_element_" + this.value) – ron_g Jan 05 '18 at 13:52
  • @JonasW. - var i = [0] sets the "start" of iteration to the first element of the form. – ron_g Jan 05 '18 at 13:52
  • 1
    Shure? I dont think that an array literal will help there. – Jonas Wilms Jan 05 '18 at 14:07
  • 1
    @JonasW. you're right of course - I was just looking into that. When I first looked at how it should be done, I used the code from https://www.safaribooksonline.com/library/view/javascript-cookbook/9781449390211/ch09s09.html – ron_g Jan 05 '18 at 14:26
  • 2
    @RonaldGreeff: LOL! Ah, well, we all make mistakes. Unfortunate that it got past the author, editor, and technical reviewer(s). :-) BTW, you may be wondering why it works: It's because arrays are actually objects, and array indexes are actually property names, which are strings (or, as of ES2015+, Symbols, but strings mostly). So the `i` in `array[i]` is coerced to string if it isn't a string. It just happens that `String([0])` is `"0"` because when you coerce an array to string, it calls `join(",")`, and so on an array with only one entry, it results in the string version of the entry. – T.J. Crowder Jan 05 '18 at 14:53

4 Answers4

2

A few notes:

  1. getElementsByName returns a list of elements. That list doesn't have a style property (the entries on the list do).

  2. ids must be unique, you can't put the same ID on multiple elements. Use a class to group elements together.

  3. data-* attributes would probably be a better choice than name, since you can use them on any element type.

  4. If you want to show/hide elements based on the radio button value, you'll need to select all of the elements you show/hide, not just the one matching the radio button's value. Then loop through that list, showing/hiding depending on whether they match the selected value.

  5. The load event happens very late in the page load cycle (e.g., right at the end). Instead of using load, put your script tags at the very end of your document, just before the closing </body> tag, and do your event hook-up immediately.

  6. I'd probably use a class to toggle visibility rather than style.display.

  7. You can get the attribute from an element via getAttribute.

  8. You can use querySelectorAll to get a list of elements matching any CSS selector. Use it on document to look globally, or on a specific element to only look within that element. For instance, to get a list of elements in the document with an attribute called data-val, you'd use the CSS selector [data-val], e.g. `document.querySelectorAll("[data-val]").

Here's a version of your snippet with some minimal updates; see comments:

// I'd use querySelectorAll rather than the old-style
// forms and elements collections (but those work too)
var radios = document.querySelectorAll("#picker input[type=radio]");
// Initial value is 0, not [0]
for (var i = 0; i < radios.length; i++) { // You were missing { on this line
    // Recommend modern event handling, not onclick
    radios[i].addEventListener("click", radioClicked);
}

function radioClicked() {
    var hidden = document.getElementById("hidden_elements");
    hidden.classList.remove("hidden");
    // Get all elements within it that have a data-val attribute
    var list = hidden.querySelectorAll("[data-val]");
    for (var i = 0; i < list.length; ++i) {
        var el = list[i];
        // Add/remove the hidden class depending on whether it matches
        el.classList.toggle("hidden", el.getAttribute("data-val") != this.value);
    }
}
.hidden {
  display: none;
}
<form id="picker" method="post" action="">
Item 1: <input type="radio" name="group1" value="one" />
Item 2: <input type="radio" name="group1" value="two" />
Item 3: <input type="radio" name="group1" value="three" /><br />
<br />
<!-- Hide this initially with markup rather than code -->
<div id="hidden_elements" class="hidden">
<!-- Use data-val to identify them -->
Input 1: <input type="text" data-val="one" />
Input 2: <input type="text" data-val="two"  />
Input 3: <input type="text" data-val="three"  /><br /><br />
</div>
<input type="submit" id="submitbutton" value="Send form" />
</form>

Some further changes you might make:

  • Wrap all the code in an IIFE so nothing is global.
  • Wrap the inputs and their labels in label elements, and toggle the visibility of them rather than the inputs directly.
  • Wrap things in containers for line breaks rather than using br.
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thank you for an amazingly informative answer. I've spent the last couple of hours trying to understand your approach and learnt a lot in the process. Couple of questions if I may - 1. " document.querySelectorAll("#picker input[type=radio]"); " - why would you not just use #picker? Why add input and it's type? 2. " hidden.querySelectorAll("[data-val]") " - why is it "[data-val]" and not simply "data-val" "? (it may be related to your other comments about coercion. I have yet to look into that. Thanks! – ron_g Jan 05 '18 at 22:22
  • And for a bonus question, how would I go about setting a default element to be selected? I've tried adding "checked" to the div of the data-val="one" element listener, and also " document.getElementById("id1").checked = true " in the script. Neither work as I guess they aren't triggering the EventListener I assume – ron_g Jan 05 '18 at 22:42
  • 1
    @RonaldGreeff: 1. Because I'm selected the inputs, not the div containing them. 2. Because that's how CSS attribute selectors work. `data-val` on its own would be looking for an element whose tag name is `data-val`, not an element that has an *attribute* called `data-val`. I suggest reading up on [CSS selectors](http://www.w3.org/TR/css3-selectors/). – T.J. Crowder Jan 06 '18 at 08:43
  • 1
    3. Right, setting `checked` in the HTML doesn't fire the event handler. You'd want to do that yourself when setting things up (that initial loop calling `addEventListener` seems like a good place to check the `checked` propery on the input). That means either calling the handler with [`Function#call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call) or reworking it slightly to use a parameter instead of `this`. – T.J. Crowder Jan 06 '18 at 08:44
1

You could do something like this :

  1. Add a common class on the elements you want to toggle (can wrap label and field in span)

  2. Toggle their display, can use a css class for hiding and a class same as the value of the checkbox

function radioClicked(e) {
  //hide previously shown 
  var elem = document.getElementById("hidden_elements").getElementsByClassName("shown")[0];
  if (elem) {
    elem.classList.remove("shown");
    elem.classList.add("hide");
  }

  //show currently selected
  elem = document.getElementById("hidden_elements").getElementsByClassName(e.value)[0];
  elem.classList.remove("hide");
  elem.classList.add("shown");
}
.hide {
  display: none;
}
<form id="picker" method="post" action="">
  Item 1: <input type="radio" name="group1" value="one" onclick="radioClicked(this)" /> 
  Item 2: <input type="radio" name="group1" value="two" onclick="radioClicked(this)" /> 
  Item 3: <input type="radio" name="group1" value="three" onclick="radioClicked(this)" /><br />
  <br />
  <div id="hidden_elements">
    <span class="hide one elem">Input 1: <input type="text" id="intext" /></span>
    <span class="hide two elem">Input 2: <input type="text" id="intext2"  /></span>
    <span class="hide three elem">Input 3: <input type="text" id="intext3"  /></span>
  </div>
  <input type="submit" id="submitbutton" value="Send form" />
</form>
Himanshu Tyagi
  • 5,201
  • 1
  • 23
  • 43
1

My solution changes the values of the radio buttons to the numeric value rather than the the text value. This enabled it to be used to select the input element by id and appending the number.

Each time radioClicked() is run it hides all the inputs and only shows the selected one. To do this I've wrapped all the inputs and their labels in span elements.

window.onload=function() {
  
  //  attach the click event handler to the radio buttons
  var radios = document.forms[0].elements["group1"];
  for (var i = [0]; i < radios.length; i++)
    radios[i].onclick=radioClicked;
    
}

function radioClicked() {

   var allRadio = document.querySelectorAll('#hidden_elements span');
      
   Array.prototype.forEach.call(allRadio, function(el, i){
      el.style.display="none";
   });
   
   var selectedRadio = this.value;
   document.getElementById("intext" + selectedRadio).parentNode.style.display="block";
   
}
#hidden_elements span {
display: none;
}
<form id="picker" method="post" action="">
Item 1: <input type="radio" name="group1" value="1" />
Item 2: <input type="radio" name="group1" value="2" />
Item 3: <input type="radio" name="group1" value="3" /><br />
<br />
<div id="hidden_elements">
<span>Input 1: <input type="text" id="intext1" /></span>
<span>Input 2: <input type="text" id="intext2"  /></span>
<span>Input 3: <input type="text" id="intext3"  /></span><br /><br />
</div>
<input type="submit" id="submitbutton" value="Send form" />
</form>
1

My solution add extra div for all inputs and id for this div

window.onload = function() {

    var hide = document.getElementsByClassName("hidden");
    for (var i = [0]; i < hide.length; i++)
        hide[i].style.display = "none";


    //  attach the click event handler to the radio buttons
    var radios = document.forms[0].elements["group1"];
    for (var i = [0]; i < radios.length; i++)
        radios[i].onclick = radioClicked;
}


function radioClicked() {
    var hide = document.getElementsByClassName("hidden");
    for (var i = [0]; i < hide.length; i++)
        hide[i].style.display = "none";
    document.getElementById("hidden_" + this.value).style.display = "block"
}
<form id="picker" method="post" action="">
Item 1: <input type="radio" name="group1" value="one" />
Item 2: <input type="radio" name="group1" value="two" />
Item 3: <input type="radio" name="group1" value="three" /><br />
<br />
<div id="hidden_elements">
  <div class="hidden" id="hidden_one">Input 1: <input type="text"  /></div>
  <div class="hidden" id="hidden_two">Input 2: <input type="text" /></div>
  <div class="hidden" id="hidden_three">Input 3: <input type="text"  /><br /><br /></div>
</div>
<input type="submit" id="submitbutton" value="Send form" />