Search code examples
javascriptcssfilterdropdown

Using only JS and css, can I implement a filter, so that: clicking/tapping within a JS dropdown menu shows/hides page divs by data-type?


With the help of SO member the.marolie I learned how to implement a JS filter/selector [the 'demo JS filter']. I have this working perfectly on a test page: it shows/hides divs according to "data-types" assigned to them in the body html. However, the selection is made by sliding down a <select> dropdown list and letting go at the preferred option. That's not quite what I want.

I want to utilise my existing nav-bar dropdown ['my dropdown'] as the filter/selector. I especially want to retain the existing interactivity of my dropdown, whereby one click/tap reveals the whole of the dropdown content block, and one click outside the content block closes it.

I want the elements within my dropdown to represent various show/hide <div> 'options' for the html page, and enable the user to choose from these via an additional click/tap (essentially what the demo JS filter does, but at the instigation of a click/tap). Once revealed via a nav-bar click/tap, the whole dropdown content block has to stay on-screen -as it currently does- for this to be practically possible.

After making my dropdown identifiable via id="media-selector-demo" and name="filter" I was hoping that I could assign the demo JS filter's <option> elements to the <a> elements in it, and the whole thing would function like the <select> dropdown of the demo JS filter. I had a vague idea that using <a> elements might obviate the need for another onClick in the JS. I've tried various combinations of <a> and <option> elements, but nothing has worked yet.

Do I need another onClick to invoke the JS filter via my dropdown? or Can I invoke the JS filter via 'active' <a> status?

I'm struggling by trial and error.

Here are what I think are the relevant sections of code pertaining to all discussed above:

My dropdown is based on the following code. JS in the page head:

/* When the user clicks on the button, 
toggle between hiding and showing the dropdown content */
function myDropdownJS() {
  document.getElementsByClassName("navbarDROPDOWN-JS")[0].classList.toggle("show");
}

// Close the dropdown if the user clicks outside of it
window.onclick = function(e) {
  if (!e.target.matches('.dropbtn')) {
  var myDropdown = document.getElementsByClassName("navbarDROPDOWN-JS")[0];
    if (myDropdown.classList.contains('show')) {
      myDropdown.classList.remove('show');
    }
  }
}

My dropdown html in nav bar (most of the css is just design styling):

<span class="dropdown" onclick="myFunction()">
<a class="dropbtn navbarDROP-ICON" style="opacity:0.5; padding-top:0px;">menu</a>
<a class="dropbtn navbarDROP-TXT" style="opacity:0.5">menu&nbsp;&nbsp;</a>
<a class="dropbtn navbarDROP-TXT">Career Works by Date&nbsp;&nbsp;</a>
<div class="dropdown-content navbarDROPDOWN-JS" >
    <a class="tag-bgd-INSTLLN" href="#">Installations (all media)</a>
    <a class="tag-bgd-MOVIMG" href="#">Works with moving image (inc. vid/film releases)</a>
    <a class="tag-bgd-SNDMUS" href="#">...with sound and music (inc. sound/music releases)</a>
    <a class="tag-bgd-PHOTO" href="#">...with photographs</a>
    <a class="tag-bgd-DRAW" href="#">...with drawing (inc. 2D works)</a>
    <a class="tag-bgd-TXT" href="#">...with text</a>
    <a class="tag-bgd-PERF" href="#">...with performative action</a>
    <a class="tag-bgd-COLPUB" href="#">Collaborative and public works</a>
    <a class="tag-bgd-OBJDEV" href="#">>Objects, garments, devices</a>
    <a class="tag-bgd-EDPUB" href="#">Editions, publications</a>
    <a class="tag-bgd-CAREER" href="#">Career Works by Date</a>
  </div>

Above: the <a href> elements were going to contain URLs for alternatively styled pages. There is no need for these if I can enable the user to selectively show/hide parts of just this one page, via this dropdown.

The demo JS filter is based on the following code (via SO user the.marolie). JS at page end:

var select = document.getElementById('media-selector-demo');
var filter;

select.addEventListener("change", function() {
  filter = select.value;
    
  var elements = document.querySelectorAll('.wk-date_ITEM');
  elements.forEach((el) => {
      var type = el.dataset.type.split(', ');
    if (type.includes(filter)) {
    
      el.classList.remove('hide-by-media');
    } else {
      el.classList.add('hide-by-media');
    }

  })
});

Demo JS filter CSS:

.hide-by-media {
    display: none;
}

Demo JS filter html in page body:

 <select id="media-selector-demo" name="filter">
    <option value="INSTLLN"> Installations (all media)</option>
    <option value="MOVIMG"> Works with moving image (inc. vid/film releases)</option>
    <option value="SNDMUS" >...with sound and music (inc. sound/music releases)</option>
  </select>   

Example div in page body (there are 80-100 of these):

<!-- ++++++++++ START FULL-WIDTH LIST ENTRY '2017 STATE OF DREAD' ++++++++++ -->
<div id="state-of-dread" class="w3-container wk-date_ITEM" data-type="INSTLLN, SNDMUS">
     <div class="w3-container wk-date_TXT-IMG">
        <div class="wk-date_GRID">          
          
          <div class= "h3 wk-date_DATE"> 2017 </div>
          <div class="wk-date_TTL"><h1>State of Dread</h1></div>
         <div class="h2 wk-date_KIND-1" >Installation</div>
          <div class="p wk-date_KIND-2" ><span class="sound">Sound</span>, for x2 interconnected rooms.<br>AB, CD, EF, Solo exhibition (as trio), Ohrenhoch sound gallery, Berlin.</div>
          <div class="wk-date_IMG">
          <div class="w3-container w3-right wk-date_IMG-BOX-LSCP">
          <img src="../../imgs/INSTALLATION-EVENT/2017_dread_thmb.jpg"
          alt="'xx' by Andrew Stones, installation view, xx"></div>
          </div>
       
        </div>
    </div>
</div>
<!-- ++++++++++ END FULL-WIDTH LIST ENTRY '2017 STATE OF DREAD' ++++++++++ -->

Demo JS filter: JS at end of page:

<script type="text/javascript">
var select = document.getElementById('media-selector-demo');
var filter;

select.addEventListener("change", function() {
  filter = select.value;

  var elements = document.querySelectorAll('.wk-date_ITEM');
  elements.forEach((el) => {
      var type = el.dataset.type.split(', ');
    if (type.includes(filter)) {
      el.classList.remove('hide-by-media');
    } else {
      el.classList.add('hide-by-media');
    }

  })
});
</script>

Solution

  • What you would need to do is change the event listener from select, change to drop down element, click. you would also need to add the values of the options from the select as data-value attributes on the drop down elements.

    1 - add a data-value attribute to the elements to represent what to hide

     <a class="tag-bgd-INSTLLN" href="#" data-value="INSTLLN">
    

    2 - target the drop down elements you want to attach the event listener to.

    const dropDownElements = document.querySelectorAll('.dropdown-content a')
    

    3 - attach event listeners to the selected targets (PS. the e in the function stands for event, click event listener produces an event object)

    dropDownElements.forEach((dropDownElement) => {
        dropDownElement.addEventListener('click',(e)=>{
            const filter = e.target.dataset.value;
        })
    })
    

    4 - the rest is just adding the rest of the filter used in the demo js filter

    dropDownElements.forEach((dropDownElement) => {
        dropDownElement.addEventListener("click", (e) => {
            const filter = e.target.dataset.value
            var elements = document.querySelectorAll(".wk-date_ITEM")
            elements.forEach((el) => {
                var type = el.dataset.type.split(", ")
                if (type.includes(filter)) {
                    el.classList.remove("hide-by-media")
                } else {
                    el.classList.add("hide-by-media")
                }
            })
        })
    })