jQuery search filter for ACF custom fields in WooCommerce

I needed a solution to filter by custom fields that I’d added to the back end of a WooCommerce site and decided a solution to “roll-my-own” would be the most appropriate.

Here’s the php I used to make the checkbox fields:

// if we're on the shop archive or category pages
if(is_shop() || is_product_category()) {
  // get all the fields choices possible

  $fa = array( //filter array
    'product_type',
    'region',
    'accreditation_linked_to',
    'curriculum',
  );

  foreach($fa as $filter){
    echo '<div class="' . $filter . '" ><h5>' . $filter . '</h5>' ;

    $filter_choice = get_field_object($filter);
    if( $filter_choice['choices'] ) {
        foreach( $filter_choice['choices'] as $value => $label ) {
                echo '<input class="' . $filter . ' ' . str_replace(' ', '-', strtolower($label)) . ' filterbox" type="checkbox" name="' . str_replace(' ', '-', strtolower($value)) . '" id="' . str_replace(' ', '-', strtolower($label)) . '"> ' . $label . '<br>';
        }
    }
    echo '</div>' ;
  }
}

I targeted the list item at the beginning of each product in the list page template. Here’s the solution I used to show the data attributes in each <li> on the content-product.php WooCommerce template:

wc_product_class( '', $product );

$fa = array( //filter array
	'product_type',
	'region',
	'accreditation_linked_to',
	'curriculum',
);

// get the product id
global $product;
$fields = get_fields($product->get_id());

foreach ($fa as $key => $filter_array_item) {

	// reset attr_concat to empty string at beginning of each loop
	$attr_concat = '';

	if(is_array($fields[$filter_array_item])) {
		foreach($fields[$filter_array_item] as $attr) {

			$attr_concat .= str_replace(' ', '-', $attr) . ' ' ;
		}
		echo ' data-' . $filter_array_item . '="' . strtolower($attr_concat) . '" ';
	}
	else {
			// $attr needs to be lowercased and hyphenated where spaces occur.
			echo ' data-' . $filter_array_item . '="' . strtolower($fields[$filter_array_item]) . '" ';
	}

}

Here’s the initial Javascript filtering solution I put together, which worked on my fist set of checkbox fields but not on others:

if( product_type.length  > 0 ) {

  // if any values, hide all + show appropriate ones later
  $('.products .product').hide();

  $.each(product_type, function() {
        
    // get name to filter by
    var filterName = $(this).attr('name');
    $('[data-product_type~="'+filterName+'"]')
    .fadeIn('fast');

    // in the .products div, find .product 
    $('.products').find('.product')
    .filter(function () {
        
    return $(this).attr('data-product_type') == filterName;     
    }).fadeIn('fast');

  });
}

Ultimately this solution only worked for the product_type checkboxes – my first set of filters only ever had a single element to filter by and the chosen approach, using .filter wasn’t effective at showing all the others that had more than one data-attribute choice.

The key was to drop the filter function and get more basic with the solution. This method relies on the markup targeting and uses the tilde before the equals sign ‘~=’ to target data attributes that contain the subsequent text, even if they don’t exactly match it in entirety. Here’s the eventual code that worked for the full set of data-attributes to show:

if( product_type.length || region.length || accreditation_linked_to.length || curriculum.length ) {

  // if any values, hide all + show appropriate ones later
  $('.products .product').hide();

  if( product_type.length > 0 ) {
    $.each(product_type, function(){

        var filterName = $(this).attr('name');

        $('[data-product_type~="'+filterName+'"]')
        .fadeIn('fast');

    });
  }

  if( region.length > 0 ) {
    $.each(region, function(){

        var filterName = $(this).attr('name');

        $('[data-region~="'+filterName+'"]').fadeIn('fast');

    });
  }

  if( accreditation_linked_to.length > 0 ) {
    $.each(accreditation_linked_to, function(){

        var filterName = $(this).attr('name');

        $('[data-accreditation_linked_to~="'+filterName+'"]')
        .fadeIn('fast');

    });
  }

  if( curriculum.length > 0 ) {
    $.each(curriculum, function(){

        var filterName = $(this).attr('name');

        $('[data-curriculum~="'+filterName+'"]').fadeIn('fast');

    });
  }
}

I then managed to refactor the code into the following, so more can be added dynamically from here on out.

$('input.filterbox').on('click', function() {

    // save checked items to array
    var product_type = $('.product_type .filterbox:checked');

    var region = $('.region .filterbox:checked');

    var accreditation_linked_to = $('.accreditation_linked_to .filterbox:checked');

    var curriculum = $('.curriculum .filterbox:checked');

    // save checked items to array of arrays
    var filterArray = [product_type, region, accreditation_linked_to, curriculum]

    filterArray.forEach(toggleFilterArray);

    // function show/hide filter array
    function toggleFilterArray(item, i, arr) {
      if( item.length > 0 ) {

        // remove the content
        $('.products .product').hide();

        $.each(item, function(){

            // find item to be shown
            var filterName = $(this).attr('name');

            // find target data-attribute
            var filterPlace = $(this).attr('class').replace(' '+filterName, '').replace(' filterbox', '');

            // fade in target element
            $('[data-'+filterPlace+'~="'+filterName+'"]').fadeIn('fast');
        });
      }
    }

  });

Leave a Reply

Your email address will not be published. Required fields are marked *