1

I've carried around a really useful JavaScript function for a while, not entirely sure of the origin (probably here on Stack Overflow) but it's certainly not something I've written as I know very little JS.

It basically reveals form sections based on the chosen select option. It works a charm when used once, however I'm now in a situation whereby I have a fairly complex form and need to use it multiple times. The obvious method is to copy\paste and simply rename each function thus making it unique. However, that's a lot of replicated code.

My issue is if I re-use it, the two select fields interfere with each other. I've tried seeing if I can lock it down or it isolate is using an ID but I'm struggling.

Minimum, reproducible example:

var current;

function reveal(element) {
  if (current !== undefined) {
    var chosen = document.getElementById(current);
    chosen.classList.remove("visible");
    chosen.classList.add("hidden");
  }
  
  var fetchMe = element.options[element.selectedIndex].getAttribute('data-show');
  
  if (fetchMe !== null) {
    current = fetchMe;
    var fetched = document.getElementById(fetchMe);
    fetched.classList.remove("hidden");
    fetched.classList.add("visible");
  }
}
.hidden {
  display: none;
}

.visible {
  display: block;
}
<h2>Knowledge</h2>

<select onchange="reveal(this)">
  <option>Select...</option>
  <option data-show="known">Known</option>
  <option data-show="unknown">Unknown</option>
</select>

<div class="hidden" id="known">
  <input type="text" name="known" value="Known">
</div>

<div class="hidden" id="unknown">
  <input type="text" name="unknown" value="Unknown">
</div>

<h2>Superheroes</h2>

<select onchange="reveal(this)">
  <option>Select...</option>
  <option data-show="batman">Batman</option>
  <option data-show="superman">Superman</option>
</select>

<div class="hidden" id="batman">
  <input type="text" name="batman" value="Batman">
</div>

<div class="hidden" id="superman">
  <input type="text" name="supermann" value="Superman">
</div>

Ideally I want to be ringfence it, or use an ID to limit it.

Also available as a Fiddle.

4 Answers 4

2

I kept your logic.
Now your select need id (here id1, id2 for the example)
Your variable "current" is now an object, where each property is an id of select

Be careful with 'var' usage. You should use 'const' or at least 'let' to avoid side effects

var current = {};

function reveal(element) {
  const idSelect = element.id
  if (current[idSelect] !== undefined) {
    var chosen = document.getElementById(current[idSelect]);
    chosen.classList.remove("visible");
    chosen.classList.add("hidden");
  }
  var fetchMe = element.options[element.selectedIndex].getAttribute('data-show');
  if (fetchMe !== null) {
    current[idSelect] = fetchMe;
    var fetched = document.getElementById(fetchMe);
    fetched.classList.remove("hidden");
    fetched.classList.add("visible");
  }
}
.hidden {
  display: none;
}

.visible {
  display: block;
}
<h2>Knowledge</h2>

<select onchange="reveal(this)" id="id1">
  <option>Select...</option>
  <option data-show="known">Known</option>
  <option data-show="unknown">Unknown</option>
</select>

<div class="hidden" id="known">
  <input type="text" name="known" value="Known">
</div>

<div class="hidden" id="unknown">
  <input type="text" name="unknown" value="Unknown">
</div>

<h2>Superheroes</h2>

<select onchange="reveal(this)" id="id2">
  <option>Select...</option>
  <option data-show="batman">Batman</option>
  <option data-show="superman">Superman</option>
</select>

<div class="hidden" id="batman">
  <input type="text" name="batman" value="Batman">
</div>

<div class="hidden" id="superman">
  <input type="text" name="supermann" value="Superman">
</div>

Sign up to request clarification or add additional context in comments.

Comments

1

I'm not sure what the point of the external variable is considering you have the data you need in the option attributes. This can be vastly simplified to not even require an argument.

Also, let's use an event listener instead of inline JavaScript.

function reveal() {
  // hide all
  document.querySelectorAll('.hidden').forEach(el => {
    el.style.display = 'none';
  });

  // show for each select
  document.querySelectorAll('select').forEach(el => {
    const selectedVal = el.selectedOptions[0].dataset.show;

    if (selectedVal) {
      document.getElementById(selectedVal).style.display = 'block';
    }
  });
}

document.querySelectorAll('select.special').forEach(el => {
  el.addEventListener('change', reveal);
});
.hidden {
  display: none;
}
<h2>Knowledge</h2>

<select class="special">
  <option>Select...</option>
  <option data-show="known">Known</option>
  <option data-show="unknown">Unknown</option>
</select>

<div class="hidden" id="known">
  <input type="text" name="known" value="Known">
</div>

<div class="hidden" id="unknown">
  <input type="text" name="unknown" value="Unknown">
</div>

<h2>Superheroes</h2>

<select class="special">
  <option>Select...</option>
  <option data-show="batman">Batman</option>
  <option data-show="superman">Superman</option>
</select>

<div class="hidden" id="batman">
  <input type="text" name="batman" value="Batman">
</div>

<div class="hidden" id="superman">
  <input type="text" name="supermann" value="Superman">
</div>

5 Comments

Aren't you scared that looping on each value of each select and do an action on them can affect performances ?
Only if there are 10,000 selects in the page. :) This is how millions of dropdown menus and accordion structures are handled. It's usually not a concern.
I mean it is always the debate, easier/faster to handle with "low" performances or the opposite. Even if you are right in this case, the user will probably not see any difference
Thanks for your efforts on this, I'm sure this is a great way of achieving the desired result but it's so fundamentally different I'd need to spend some time getting my head round it, I also prefer using classes rather than applying styles inline, as the style can vary from block to flex etc. depending on the layout. Thanks again!
Everything up there is pretty basic. I suggest looking for cleaner patterns rather than sticking with what's familiar. The switch from inline styles to classes is trivial if you prefer.
1

Instead of keeping selected input in a global variable which make the function hard to reuse, work through each select options separately:

function reveal(element) {
  var options = element.options;
  for (var i = 0; i < options.length; i++) {
    var option = options[i].getAttribute('data-show');
    var chosen = option && document.getElementById(option);
    if (chosen !== null) {
      if (i === element.selectedIndex) {
        chosen.classList.remove("hidden");
        chosen.classList.add("visible");
      } else {
        chosen.classList.remove("visible");
        chosen.classList.add("hidden");
      }
    }
  }
}
.hidden {
  display: none;
}

.visible {
  display: block;
}
<h2>Knowledge</h2>

<select onchange="reveal(this)">
  <option>Select...</option>
  <option data-show="known">Known</option>
  <option data-show="unknown">Unknown</option>
</select>

<div class="hidden" id="known">
  <input type="text" name="known" value="Known">
</div>

<div class="hidden" id="unknown">
  <input type="text" name="unknown" value="Unknown">
</div>

<h2>Superheroes</h2>

<select onchange="reveal(this)">
  <option>Select...</option>
  <option data-show="batman">Batman</option>
  <option data-show="superman">Superman</option>
</select>

<div class="hidden" id="batman">
  <input type="text" name="batman" value="Batman">
</div>

<div class="hidden" id="superman">
  <input type="text" name="supermann" value="Superman">
</div>

Comments

1

I realise there is already some great answers here (including an accepted one) but here's my take on it:

function reveal(selectElem) {
  //for each of this selectElem's options
  for (const option of selectElem) {
    var div = document.getElementById(option.getAttribute('data-show'));//find the corresponding div
    if(div!=undefined){//if it exists
      div.classList.add("hidden");//hide it
      if(option.selected){//if its selected
        div.classList.remove("hidden");//show it
      }
    }
  }
}
.hidden {
  display: none;
}
<h2>Knowledge</h2>

<select onchange="reveal(this)">
  <option>Select...</option>
  <option data-show="known">Known</option>
  <option data-show="unknown">Unknown</option>
</select>

<div class="hidden" id="known">
  <input type="text" name="known" value="Known">
</div>

<div class="hidden" id="unknown">
  <input type="text" name="unknown" value="Unknown">
</div>

<h2>Superheroes</h2>

<select onchange="reveal(this)">
  <option>Select...</option>
  <option data-show="batman">Batman</option>
  <option data-show="superman">Superman</option>
</select>

<div class="hidden" id="batman">
  <input type="text" name="batman" value="Batman">
</div>

<div class="hidden" id="superman">
  <input type="text" name="supermann" value="Superman">
</div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.