【问题标题】:Show/hide optgroup based on checkboxes根据复选框显示/隐藏 optgroup
【发布时间】:2018-05-21 09:59:55
【问题描述】:

所以基本上我想要一个像这个问题here 这样的解决方案,但“主人”是<checkbox> 而不是<select>
例如,我有四个复选框和一个带有四个 optgroup 的选择。我只想显示使用复选框选中的这些 optgroup。
我对javascript很陌生,想不出解决方案。我尝试调整我发布的问题的解决方案,但没有成功。我在互联网上也找不到完全相同的问题。
感谢您的帮助。

编辑:
这是代码(虽然是多余的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>

当然,它应该根据更改的选择进行自我调整。
这里是Javascript(来自其他问题的ctrl + c,ctrl + v):

$(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();
        });
});

【问题讨论】:

    标签: javascript html select checkbox optgroup


    【解决方案1】:

    CSS 替代方案(但我建议使用 valid identifiers 而不是数字):

    [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 来取消选择未显示的选项:

    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>

    【讨论】:

    • 感谢您的回答。不过,我必须把这个 CSS 代码放在哪里?当我在 IE 11 中运行你的代码 sn-p 时(只有浏览器可用)它不起作用。有什么想法吗?
    【解决方案2】:

    最简单的解决方案,使用jQuery,我能想到的如下:

    // 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.

    或者,上面可以很容易地重写以使用 DOM API 或“普通”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.

    参考资料:

    【讨论】:

    • 感谢您的详细解释,我会在我回来工作时尝试一下。
    • 嗯,我实现了它,现在我在调用.lengthlabels 时得到一个空指针异常。知道为什么我会收到此错误吗?
    • 我的第一个想法是this.labelsnull;如果是这种情况,您可以简单地将三元组的第一部分修改为el.labels &amp;&amp; el.labels.length。在三元组之前的行中,也可能值得调用console.log(el.labels) 来确定。
    • 好吧,现在我在三元组中有this.labels &amp;&amp; this.labels.length,我不再收到错误,但它似乎也不起作用。我运行了您的代码 sn-p 并在 jsfiddle 上对其进行了测试,但它没有按预期工作。 IE 11 不支持这个吗?
    猜你喜欢
    • 1970-01-01
    • 2013-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多