Search code examples
jqueryfilteringcustom-data-attribute

Show elements matching multiple selected data attributes


I am creating a set of filters and once they are selected, corresponding divs should show based on ALL of the attributes being selected.

I can get this to work one at a time, but my issue is when trying to get them to work together. I feel like there is probably a better approach to this in general?

Here are my select menus:

<div class="filters">
 <select class="filter-location">
   <option value="" disabled="" selected="">Location</option>
   <option data-location="Remote">Remote – United States</option>
   <option data-location="Portland, OR">Portland, OR</option>
   <option data-location="Seattle, WA">Seattle, WA</option>
 </select>
 <select class="filter-team">
   <option value="" disabled="" selected="">Team</option>
   <option data-team="Sales">Sales</option>
   <option data-team="Support">Support</option>
   <option data-team="Management">Management</option>
 </select>
 <select class="filter-type">
   <option value="" disabled="" selected="">Type</option>
   <option data-type="Full-Time">Full-Time</option>
   <option data-type="Contract">Contract</option>
 </select>
</div>

Here is a simplified version of my HTML markup:

<div class="job" data-team="Sales" data-location="Remote" data-type="Full-Time">
  <p>Job Content Here</p>
</div>
<div class="job" data-team="Management" data-location="Portland, OR" data-type="Contract">
  <p>Job Content Here</p>
</div>
<div class="job" data-team="Sales" data-location="Seattle, WA" data-type="Full-Time">
  <p>Job Content Here</p>
</div>

Here is my jQuery that works for showing one filter at a time:

<script type="text/javascript">
  jQuery(document).ready(function($) {
    $('.filters select').on('change', function() {

      var location_value = $(':selected', this).data('location');
      var team_value = $(':selected', this).data('team');
      var type_value = $(':selected', this).data('type');

      $('.job').hide();

      $(".job").each(function( index ) {
        if ($(this).data('location') == location_value) {
          $(this).show();
        }
        if ($(this).data('team') == team_value) {
          $(this).show();
        }
        if ($(this).data('work_type') == type_value) {
          $(this).show();
        }
      });

    });
  }); 
</script>

Solution

  • Following creates an array of filter objects for each <select> that has a value that looks like:

    [
      {
        "filter": "location",
        "value": "Remote"
      },
      {
        "filter": "team",
        "value": "Sales"
      }
    ]
    

    It then uses Array#every() to make sure that each filter matches a job's data- attributes to determine whether to show it or not.

    Note I made the <select> elements a bit more generic and added a data-filter attribute to each one and just used value on the <option> elements

    const $sels = $('.filters select'),
          $jobs = $('.job')
    
    $sels.change(function(){
        const filters = $sels.filter(function(){
            return !!this.value
        }).map(function(){
           return {filter: $(this).data('filter'), value : this.value} 
        }).get();
        //console.log('Filters', filters)    
        
        // hide all jobs then filter the ones to show
        const $filterJobs = $jobs.hide().filter(function(){
            const data = $(this).data()
            return filters.every(function(obj){
               return data[obj.filter] === obj.value 
            });
        }).show(); 
    
        // toggle "no-match" depending on length of filter jobs collection
        $('#no-match').toggle( !$filterJobs.length )
    
    });
    .job, #no-match {
      display: none
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div class="filters">
      <select data-filter="location">
        <option value="">Location</option>
        <option value="Remote">Remote – United States</option>
        <option value="Portland, OR">Portland, OR</option>
        <option value="Seattle, WA">Seattle, WA</option>
      </select>
      <select data-filter="team">
        <option value="">Team</option>
        <option value="Sales">Sales</option>
        <option value="Support">Support</option>
        <option value="Management">Management</option>
      </select>
      <select data-filter="type">
        <option value="">Type</option>
        <option value="Full-Time">Full-Time</option>
        <option value="Contract">Contract</option>
      </select>
    </div>
    <div id="no-match">
       <h3>No Match</h3>
    </div>
    
    <div class="job" data-team="Sales" data-location="Remote" data-type="Full-Time">
      <p>Sales Remote Full Time</p>
    </div>
    <div class="job" data-team="Management" data-location="Portland, OR" data-type="Contract">
      <p>Management Portland Contract</p>
    </div>
    <div class="job" data-team="Sales" data-location="Seattle, WA" data-type="Full-Time">
      <p>Sales Seattle Full Time</p>
    </div>