简体   繁体   中英

In Vanilla JavaScript, how do I show/hide <span>s with multiple class names?

So, say I have this in my body section of my HTML5 doc:

    <p><input type="checkbox" checked onchange="ShowHide('Book1', this);"> Book 1</p>
    <p><input type="checkbox" checked onchange="ShowHide('Book2', this);"> Book 2</p>
    <p><span class="Book1">Show when Book 1 is checked. </span>
       <span class="Book2">Show when Book 2 is checked. </span>
       <span class="Book1 Book2">Show when either Book 1 or Book 2, or both are checked. </span></p>

with the following script in a seperate JS file:

function ShowHide(a, b) {
    var vis = (b.checked) ? "" : "none";
    var book = document.getElementsByClassName(a);
    for (var i = 0; i < book.length; i++) {
        book[i].style.display = vis;
    }   
}

It will show/hide the 'book content' for 1 & 2 just fine. But I want the content that has both class names, to show up, if either one or both are checked. But not if they both aren't selected. With 2 books, I can write out every scenario, but with 13+ books this is quite impossible. Any ideas on how to do this? Any insight would be highly appreciated!

This is a little tricky. You need to check every span (or at least every span that matches the toggled checkbox) to see if it matches any of the currently checked checkboxes.

Your current approach is to use JavaScript to identify which elements to show and hide. To do that you would need to:

  • Gather a list of all the currently checked checkbox values
  • Gather a list of all the span elements
  • Loop over all the span elements and see if they match any of the checkbox values in the list (with indexOf ) hiding or showing them as appropriate

This isn't difficult, but it involves a lot of looping and is rather tedious.

I'd approach this in a slightly different way:

Modify the class of a parent container and use CSS to control what is visible or not.

Moving the problem of deciding how to combine the various "Display this" instructions to CSS makes life a lot easier.

 document.getElementById('checkboxes').addEventListener('click', showHide); function showHide(e) { if (e.target.type !== "checkbox") { return; } var b = document.getElementById('books'); if (e.target.checked) { b.classList.add("show-" + e.target.value); } else { b.classList.remove("show-" + e.target.value); } } 
 #books p { display: none; } #books.show-book1 .book1 { display: block; } #books.show-book2 .book2 { display: block; } 
 <div id="checkboxes"> <label> <input type="checkbox" value="book1" />Book1</label> <label> <input type="checkbox" value="book2" />Book2</label> </div> <div id="books"> <p class="book1">One</p> <p class="book2">Two</p> <p class="book1 book2">Both</p> </div> 

The downside with this approach is writing out a CSS ruleset for each book class. You could generate them programatically (SASS, for instance, makes generating this sort of set of similar rules very easy).

I think an elegant solution is to keep a reference count of filters checked directly on the element using data attributes .

Every time a checkbox is clicked, associated items are iterated, and their reference count updated. Any element with no references is hidden. This keeps your runtime to a single loop, and is very scalable.

 function checker (e) { var ref, dir, itemset; if (e.target.type === 'checkbox') { dir = e.target.checked ? 1 : -1; itemset = document.getElementsByClassName(e.target.value); Array.prototype.forEach.call(itemset, function (el) { ref = dir + parseInt(el.getAttribute('data-ref'), 10); el.setAttribute('data-ref', ref); el.style.display = ref > 0 ? 'block' : 'none'; }); } } document .getElementById('checks') .addEventListener('click', checker); 
 .item { display: none; } 
 <div id="checks"> <input type="checkbox" value="one"> <input type="checkbox" value="two"> <input type="checkbox" value="three"> </div> <div id="items"> <div class="item one" data-ref="0">one</div> <div class="item two" data-ref="0">two</div> <div class="item three" data-ref="0">three</div> <item class="item one two" data-ref="0">one-two</item> </div> 

This is a little bit dirty, but it seems to work :
Caveat : you have to append a class to the inputs, could also be a data-attribute :

If the changed input is not checked, it will loop through the spans that have the same className, and check if they've got an other className. If they do, then it will check if the other input that have this className is checked or not.

 function ShowHide(inp) { var vis = inp.checked; var spans = document.querySelectorAll('span.' + inp.className); for (var i = 0; i < spans.length; i++) { if (!vis) { var this_vis; var classes = spans[i].className.split(' '); for (var c = 0; c < classes.length; c++) { this_vis = document.querySelector('input.' + classes[c]).checked; if (this_vis) break; } } spans[i].style.display = this_vis || vis ? '' : 'none'; } } 
 <p> <input class="Book1" type="checkbox" checked onchange="ShowHide(this)">Book 1</p> <p> <input class="Book2" type="checkbox" checked onchange="ShowHide(this)">Book 2</p> <p><span class="Book1">Show when Book 1 is checked. </span> <span class="Book2">Show when Book 2 is checked. </span> <span class="Book1 Book2">Show when either Book 1 or Book 2, or both are checked. </span> </p> 

I have put the data in an array. Also it has a show flag specifying the content to show.

var bookData = [
                {id: 1, name: "book 1", content: 'Show when Book 1 is checked.', show: [1, 2, 3]},
                {id: 2, name: "book 2", content: 'Show when Book 2 is checked.', show: [2]},
                {id: 3, name: "book 3", content: 'Show when Book 3 is checked.', show: [2, 3]},
                {id: 4, name: "book 4", content: 'Show when Book 4 is checked.', show: [1, 3, 4]},
                {id: 5, name: "book 5", content: 'Show when Book 5 is checked.', show: [2, 4, 5]}
            ];
 function ShowHide(a, b, c) {
                var vis = (b.checked) ? "" : "none";
                var book = document.getElementsByClassName('books');
                console.log(c);
                var el = [];
                for(var j = 0; j < book.length; j++) {
                    book[j].style.display = 'none';
                }
                for (var i = 0; i < c.length; i++) {
                    var clsname = 'book-'+ c[i];

                    el.push(document.getElementById(clsname)) ;
                    console.log(el);
                    el[i].style.display = vis;
                }
            }
            function showBooks() {
                var data = '',
                        cont=  '';
                for(var i = 0; i < bookData.length; i++) {
                    var showItems = '';
                    var items = [];
                    for(var j = 0; j < bookData[i].show.length; j++) {
                        showItems += 'Book' + bookData[i].show[j] + ' ';
                        items.push(bookData[i].show[j]);
                    }
                    data+= '<p><input id="bkid-'+ bookData[i].id+'" type="checkbox" onchange="ShowHide('+'\'Book'+bookData[i].id+'\', this, ['+ items +']);" >'+ bookData[i].name+'</p>';

                    cont += '<p><span class="books" id="book-'+bookData[i].id+'">'+bookData[i].content+'</span>';
                }
                document.getElementById("box").innerHTML = data + cont;
            }

showBooks();

In this way the code remains dynamic, and we can feed as much data of books we want. working fiddle

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM