1

I'm having trouble getting a div ('.option-other') within a parent group ('.other-row') to show/hide when the corresponding option of the select element ('.select-toggle') is selected. Right now if "other" is selected from either question set 1 or 2 it will show both of the '.option-other' divs. I tried using .parent() and .closest() as described in this solution, but can't seem to figure out the proper way to utilize it for this use case.

$(".select-toggle").change(function() {
  var oth = false;
  $(".select-toggle option:selected").each(function() {
    if ($(this).val() == "other") oth = true;
  });
  if (oth) $('.option-other').show();
  else $('.option-other').hide();

  // tried this method as well but still doesnt work
  // if (oth) $(this).closest('.other-row').children('.option-other').show();
  // else $(this).closest('.other-row').children('.option-other').hide();
}).change();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- Question set 1 -->
<div class="wrapper other-row">
  <div class="col">
    <div class="form-group">
      <label>What stuff do you eat?</label>
      <select class="select-toggle" multiple>
            <option>Pizza</option>
            <option>Cake</option>
            <option value="other">Other</option>
          </select>
    </div>
  </div>
  <div class="col">
    <div class="form-group option-other">
      <label>Other</label>
      <input type="text" placeholder="what other stuff do you like" />
    </div>
  </div>
</div>

<!-- Question set 2 -->
<div class="wrapper other-row">
  <div class="col">
    <div class="form-group">
      <label>What stuff do you drink?</label>
      <select class="select-toggle" multiple>
            <option>Water</option>
            <option>Soda</option>
            <option value="other">Other</option>
          </select>
    </div>
  </div>
  <div class="col">
    <div class="form-group option-other">
      <label>Other</label>
      <input type="text" placeholder="what other stuff do you like" />
    </div>
  </div>
</div>
randy
  • 217
  • 2
  • 9

2 Answers2

1
// you wrote:
// tried this method as well but still doesnt work
// if (oth) $(this).closest('.other-row').children('.option-other').show();
// else $(this).closest('.other-row').children('.option-other').hide();

You're close, but $.children only selects direct children of each .other-row. Since .option-other is inside .col inside .other-row, $.children can't see it. Use $.find instead.

// your original code:
var oth = false;
$(".select-toggle option:selected").each(function() {
  if ($(this).val() == "other") oth = true;
});

This sets one visibility value for the entire page: if at least one "other" option is selected, anywhere, show all the text inputs. The change event is fired for the <select> that actually changed, so focus your efforts there:

var oth = false;
$(this).children("option:selected").each(function() {
  if ($(this).val() == "other") oth = true;
});
if (oth) $(this).closest('.other-row').find('.option-other').show();
else $(this).closest('.other-row').find('.option-other').hide();

This works, but it could be cleaner. Showing or hiding an element based on a boolean is a common enough requirement that jQuery has a function for it: $.toggle. You can replace the if/else lines with

$(this).closest('.other-row').find('.option-other').toggle(oth);

Your $.each loop does one thing: set oth if there exists at least one selected <option> with a value of "other". You can get the same logic as a one-liner by using an attribute selector:

var oth = ($(this).find('option:checked[value="other"]').length !== 0);

(I changed :selected to :checked because you're already filtering on option elements, and :selected has a performance penalty.)

The final version:

$(".select-toggle").change(function() {
  var oth = ($(this).find('option:checked[value="other"]').length !== 0);
  $(this).closest('.other-row').find('.option-other').toggle(oth);
}).change();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- Question set 1 -->
<div class="wrapper other-row">
  <div class="col">
    <div class="form-group">
      <label>What stuff do you eat?</label>
      <select class="select-toggle" multiple>
            <option>Pizza</option>
            <option>Cake</option>
            <option value="other">Other</option>
          </select>
    </div>
  </div>
  <div class="col">
    <div class="form-group option-other">
      <label>Other</label>
      <input type="text" placeholder="what other stuff do you like" />
    </div>
  </div>
</div>

<!-- Question set 2 -->
<div class="wrapper other-row">
  <div class="col">
    <div class="form-group">
      <label>What stuff do you drink?</label>
      <select class="select-toggle" multiple>
            <option>Water</option>
            <option>Soda</option>
            <option value="other">Other</option>
          </select>
    </div>
  </div>
  <div class="col">
    <div class="form-group option-other">
      <label>Other</label>
      <input type="text" placeholder="what other stuff do you like" />
    </div>
  </div>
</div>

Vanilla JS version:

document.querySelectorAll('.select-toggle').forEach(el => {
  el.addEventListener('change', evt => {
    const oth = evt.target.querySelector('option:checked[value="other"]');
    evt.target
      .closest('.other-row')
      .querySelector('.option-other')
      .style.display = (oth ? '' : 'none');
  });

  // trigger change event programmatically
  const event = document.createEvent('HTMLEvents');
  event.initEvent('change', true, false);
  el.dispatchEvent(event);
});
AuxTaco
  • 4,883
  • 1
  • 12
  • 27
  • Wow. Thanks for this great write up and helping me optimize the code even better. I added another .children() before the .children("option") since I use an element in my actual use case. Would you recommend a single .find() over chaining two .children().children("element")? – randy Feb 07 '18 at 06:01
  • @randy I would personally use `$.find` over chaining `$.children` because it works whether or not an `` exists. And also less chaining means the code is easier to read. (While I was writing that part, I was thinking "`$.children` works here, right? ` – AuxTaco Feb 07 '18 at 09:25
0

Here is a solution that is a little clunky but I did it relatively quick. It's kind of a work around for having to know which of your two selectors with the same class had been selected.

Here is a working example using your code.

$(".select-toggle").change(function () {
  var oth = false;
  $(".select-toggle option:selected").each(function () {
    if ($(this).val() == "otherFood") {
      oth = true; 
      $('.option-other-food').show();
    } else {
      $('.option-other-food').hide();
    };
    if ($(this).val() == "otherDrink") {
      oth = true; 
      $('.option-other-drink').show();
    } else {
      $('.option-other-drink').hide();
    };
  });
}).change();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- Question set 1 -->
<div class="wrapper other-row">
  <div class="col">
    <div class="form-group">
      <label>What stuff do you eat?</label>
      <select class="select-toggle" multiple>
        <option>Pizza</option>
        <option>Cake</option>
        <option value="otherFood">Other</option>
      </select>
    </div>
  </div>
  <div class="col">
    <div class="form-group option-other-food">
      <label>Other</label>
      <input type="text" placeholder="what other stuff do you like"/>
    </div>
  </div>
</div>

<!-- Question set 2 -->
<div class="wrapper other-row">
  <div class="col">
    <div class="form-group">
      <label>What stuff do you drink?</label>
      <select class="select-toggle" multiple>
        <option>Water</option>
        <option>Soda</option>
        <option value="otherDrink">Other</option>
      </select>
    </div>
  </div>
  <div class="col">
    <div class="form-group option-other-drink">
      <label>Other</label>
      <input type="text" placeholder="what other stuff do you like"/>
    </div>
  </div>
</div>

Cheers!

JSONaLeo
  • 416
  • 6
  • 11
  • I see what you did here, but I was hoping to avoid all of the if else statements for each question set, since there's a lot of these "other" inputs. I was hoping to figure out a way for it to only stay within the scope of the main parent group without making unique attributes for each. – randy Feb 07 '18 at 02:39