0

So basically I want a solution like in this question here but with the "master" being <checkbox> not <select>.
So for example I have four checkboxes and a select with four optgroups. I want to show only these optgroups which are selected with the checkboxes.
I am very new to javascript and can't think of a solution. I tried adapting the solution of the question I posted but without success. I also couldn't find the exact same problem on the internet.
Thanks for your help.

Edit:
Here is the code (though redundant imo):

<input type="checkbox" id="1" name="serviceType" checked="checked" />
<label for="1">Application</label>
<input type="checkbox" id="2" name="serviceType" />
<label for="2">Infrastructure Service</label>
<input type="checkbox" id="3" name="serviceType" />
<label for="3">Platform</label>
<input type="checkbox" id="4" name="serviceType" checked="checked" />
<label for="4">Software</label>

<select name="services">                                
    <optgroup label="Application">                      <!-- Shown -->
        <option value="Office">Office</option>           
        <option value="SAP">SAP</option>             
    </optgroup>
    <optgroup label="Infrastructure Service">           <!-- Hidden -->
        <option value="Router">Router</option>                 
        <option value="Switch">Switch</option>                   
    </optgroup>
    <optgroup label="Platform">                         <!-- Hidden -->      
        <option value="Server">Server</option> 
        <option value="Client">Client</option>     
    </optgroup>
    <optgroup label="Software">                         <!-- Shown -->      
        <option value="Word">Word</option>           
        <option value="Excel">Excel</option>   
    </optgroup>
</select>

And of course it should adjust itself based on changed selections.
And here the Javascript (ctrl + c, ctrl + v from other question):

$(document).ready(function () {
    $("checkbox[name='serviceType']:eq(0)").on("change",
        function () {
            console.log(this.value);
            $("select[name='services']:eq(0)")
                .find("optgroup,option")
                .hide()
                .filter("[label='" + this.value + "'],[label = '" + this.value + "'] > *")
                .show();
        });
});
Taysumi
  • 291
  • 2
  • 16

2 Answers2

0

CSS alternative (but I recommend using valid identifiers instead of numbers) :

[id="1"]:not(:checked) ~ select > [label=Application], 
[id="2"]:not(:checked) ~ select > [label="Infrastructure Service"], 
[id="3"]:not(:checked) ~ select > [label=Platform], 
[id="4"]:not(:checked) ~ select > [label=Software] { display: none; }
<input type="checkbox" id="1" name="serviceType" checked>
<label for="1">Application</label>
<input type="checkbox" id="2" name="serviceType">
<label for="2">Infrastructure Service</label>
<input type="checkbox" id="3" name="serviceType">
<label for="3">Platform</label>
<input type="checkbox" id="4" name="serviceType" checked>
<label for="4">Software</label>
<br>
<select name="services" size=10 style="width: 166px">
    <optgroup label="Application">                      <!-- Shown -->
        <option value="Office">Office</option>
        <option value="SAP">SAP</option>
    </optgroup>
    <optgroup label="Infrastructure Service">           <!-- Hidden -->
        <option value="Router">Router</option>
        <option value="Switch">Switch</option>
    </optgroup>
    <optgroup label="Platform">                         <!-- Hidden -->      
        <option value="Server">Server</option>
        <option value="Client">Client</option>
    </optgroup>
    <optgroup label="Software">                         <!-- Shown -->      
        <option value="Word">Word</option>
        <option value="Excel">Excel</option>
    </optgroup>
</select>

JavaScript might be needed to deselect options that are not displayed:

services.value = ''
document.onclick = function(e) { if (e.target.checked === false) services.value = '' }
[id="1"]:not(:checked) ~ select > [label=Application], 
[id="2"]:not(:checked) ~ select > [label="Infrastructure Service"], 
[id="3"]:not(:checked) ~ select > [label=Platform], 
[id="4"]:not(:checked) ~ select > [label=Software] { display: none; }
<input type="checkbox" id="1" name="serviceType" checked>
<label for="1">Application</label>
<input type="checkbox" id="2" name="serviceType">
<label for="2">Infrastructure Service</label>
<input type="checkbox" id="3" name="serviceType">
<label for="3">Platform</label>
<input type="checkbox" id="4" name="serviceType" checked>
<label for="4">Software</label>
<br>
<select id="services">
    <optgroup label="Application">                      <!-- Shown -->
        <option value="Office">Office</option>
        <option value="SAP">SAP</option>
    </optgroup>
    <optgroup label="Infrastructure Service">           <!-- Hidden -->
        <option value="Router">Router</option>
        <option value="Switch">Switch</option>
    </optgroup>
    <optgroup label="Platform">                         <!-- Hidden -->      
        <option value="Server">Server</option>
        <option value="Client">Client</option>
    </optgroup>
    <optgroup label="Software">                         <!-- Shown -->      
        <option value="Word">Word</option>
        <option value="Excel">Excel</option>
    </optgroup>
</select>
Slai
  • 22,144
  • 5
  • 45
  • 53
  • Thanks for your answer. Where would I have to put this CSS code though? And when I run your code snippet in IE 11 (only browser available at work) it doesn't work. Any ideas? – Taysumi Dec 11 '17 at 09:23
0

The simplest solution, using jQuery, I can think of is the following:

// selecting all <input> elements whose 'type' attribute
// is equal to 'checkbox,' binding an event-listener to
// the 'change' event on those elements which executes the
// supplied anonymous function:
$('input[type=checkbox]').on('change', function() {

  // here we first find the <label> element(s) which are
  // attached (via either the 'for' attribute-value or by
  // nesting the <input> within the <label> element); we
  // check that this labels property is true/truthy using
  // a ternary operator. If there are <label> elements
  // available via the labels property, we select the first
  // otherwise we select the nextElementSibling of the
  // <input> (this approach does require a predictable HTML)
  // and will require change should the structure change:
  let label = this.labels.length ? this.labels[0] : this.nextElementSibling;

  // here select the <optgroup> element(s) whose 'label'
  // attribute-value is equal to the text of the <label>
  // element's textContent property, with leading and
  // trailing white-space removed (using String.prototype.trim():
  $('optgroup[label="' + label.textContent.trim() + '"]')

    // we then toggle the display of the retrieved elements,
    // using the supplied switch (this.checked), which will
    // show elements if 'this.checked' evaluates to true, and
    // hide them if it evaluates to false:
    .toggle(this.checked);

// here we trigger the 'change' event on the elements matching
// the initial selector, in order to hide, or show, the relevant
// <optgroup> elements on page-load:
}).change();

$('input[type=checkbox]').on('change', function() {
  let label = this.labels.length ? this.labels[0] : this.nextElementSibling;
  $('optgroup[label="' + label.textContent.trim() + '"]').toggle(this.checked);
}).change();
label::after {
  content: '';
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="checkbox" id="1" name="serviceType" checked="checked" />
<label for="1">Application</label>
<input type="checkbox" id="2" name="serviceType" />
<label for="2">Infrastructure Service</label>
<input type="checkbox" id="3" name="serviceType" />
<label for="3">Platform</label>
<input type="checkbox" id="4" name="serviceType" checked="checked" />
<label for="4">Software</label>

<select name="services">
  <optgroup label="Application">
    <!-- Shown -->
    <option value="Office">Office</option>
    <option value="SAP">SAP</option>
  </optgroup>
  <optgroup label="Infrastructure Service">
    <!-- Hidden -->
    <option value="Router">Router</option>
    <option value="Switch">Switch</option>
  </optgroup>
  <optgroup label="Platform">
    <!-- Hidden -->
    <option value="Server">Server</option>
    <option value="Client">Client</option>
  </optgroup>
  <optgroup label="Software">
    <!-- Shown -->
    <option value="Word">Word</option>
    <option value="Excel">Excel</option>
  </optgroup>
</select>

JS Fiddle demo.

Alternatively, the above could be quite easily rewritten to use the DOM API or 'plain' JavaScript:

// here we create a named function to handle the
// events:
function toggleIfChecked() {

  // we cache the 'this' element node, supplied from
  // EventTarget.addEventListener():
  let el = this,

    // as before we retrieve the relevant <label> element,
    // and, as before, it's equally fragile should the
    // HTML structure change:
    label = el.labels.length ? el.labels[0] : el.nextElementSibling,

    // we retrieve the relevant textContent from the <label>,
    // and - again - remove leading/trailing white-space:
    text = label.textContent.trim(),

    // here we hard-code the relevant <select> element within
    // which to search, retrieving it via
    // document.querySelector() which retrieves the first
    // element matching the supplied selector or, if no
    // such element is found, returns null:
    select = document.querySelector('select[name=services]');

  // we use Array.from() to convert the Array-like NodeList
  // returned from wrapped call to document.querySelectorAll()
  // into an Array, which allows us to use Array methods on
  // the returned Array of element nodes:
  Array.from(

    // as above we retrieve all <optgroup> elements whose
    // 'label' attribute is equal to the textContent retrieved
    // earlier:
    select.querySelectorAll('optgroup[label="' + text + '"]')

  // using Array.prototype.forEach() to iterate over the
  // Array of nodes, using arrow function syntax:
  ).forEach(

    // 'optgroup' is the current element node of the Array of
    // elements over which we're iterating; the ternary operator
    // returns 'block' (if the changed-<input> element is checked)
    // or 'none' (if the changed-<input> element is not checked);
    // this allows us to set the display property of the
    // <optgroup>'s 'style' CSSStyleDeclarationObject according
    // to the un/checked state of the changed-<input> element:
    optgroup => optgroup.style.display = el.checked ? 'block' : 'none'
  );
}

// creating an Event Object to refer to the 'change' event:
let changeEvent = new Event('change');


// using Array.from() again, to convert the nodeList
// from document.querySelectorAll() into an Array of
// nodes in order to use Array.prototype.forEach():
Array.from(

  // selecting all <input> elements whose type is set
  // to 'checkbox':
  document.querySelectorAll('input[type=checkbox]')

// iterating over the Array:
).forEach(

  // 'input' is the current <input> element of the Array of
  // <input> elements within the Array over which we're
  // iterating:
  input => {

    // because we're performing multiple actions in this
    // Arrow function expression, we're wrapping those
    // actions - or statements - in a block-scope;
    // first: we bind the toggleIfChecked() (note the lack
    // of parentheses in the call to addEventListener()),
    // to handle the 'change' event on the <input> element:
    input.addEventListener('change', toggleIfChecked);

    // then we dispatch the changeEvent Event object
    // in order to trigger that event-listener; causing
    // the function to be fired on page-load, and setting
    // the visibility of the relevant <optgroup> to the
    // appropriate state (shown or hidden):
    input.dispatchEvent(changeEvent);
  }
);

function toggleIfChecked() {
  let el = this,
    label = el.labels.length ? el.labels[0] : el.nextElementSibling,
    text = label.textContent.trim(),
    select = document.querySelector('select[name=services]');

  Array.from(
    select.querySelectorAll('optgroup[label="' + text + '"]')
  ).forEach(
    optgroup => optgroup.style.display = el.checked ? 'block' : 'none'
  );
}

let changeEvent = new Event('change');

Array.from(
  document.querySelectorAll('input[type=checkbox]')
).forEach(
  input => {
    input.addEventListener('change', toggleIfChecked);
    input.dispatchEvent(changeEvent);
  }
);
label::after {
  content: '';
  display: block;
}
<input type="checkbox" id="1" name="serviceType" checked="checked" />
<label for="1">Application</label>
<input type="checkbox" id="2" name="serviceType" />
<label for="2">Infrastructure Service</label>
<input type="checkbox" id="3" name="serviceType" />
<label for="3">Platform</label>
<input type="checkbox" id="4" name="serviceType" checked="checked" />
<label for="4">Software</label>

<select name="services">
  <optgroup label="Application">
    <!-- Shown -->
    <option value="Office">Office</option>
    <option value="SAP">SAP</option>
  </optgroup>
  <optgroup label="Infrastructure Service">
    <!-- Hidden -->
    <option value="Router">Router</option>
    <option value="Switch">Switch</option>
  </optgroup>
  <optgroup label="Platform">
    <!-- Hidden -->
    <option value="Server">Server</option>
    <option value="Client">Client</option>
  </optgroup>
  <optgroup label="Software">
    <!-- Shown -->
    <option value="Word">Word</option>
    <option value="Excel">Excel</option>
  </optgroup>
</select>

JS Fiddle demo.

References:

David Thomas
  • 249,100
  • 51
  • 377
  • 410
  • Thank you for your very well explained answer, I will try it out when I'm back at work. – Taysumi Dec 09 '17 at 21:55
  • Hmm, I implemented it and now I get a nullpointer exception when calling `.length` of `labels`. Any idea why I might get this error? – Taysumi Dec 11 '17 at 07:21
  • My first thought would be that `this.labels` is `null`; if that's the case you could simply amend the the first part of the ternary to `el.labels && el.labels.length`. It may also be worth, in the line before the ternary, calling `console.log(el.labels)` just to be sure. – David Thomas Dec 11 '17 at 08:28
  • Well, now that I have `this.labels && this.labels.length` in the ternary, I don't get the error anymore but it doesn't seem to work either. I ran your code snippet and tested it on jsfiddle and it doesn't work as intended. Isn't this supported in IE 11 or something? – Taysumi Dec 11 '17 at 08:39