0

I am writing a product filter, and ran into this issue.

I have 2 different attributes of my product filter: Tilt -- as a checkbox with "Yes" or "No", and Multibrand Compatible -- as a checkbox with Yes or No. If I click Multibrand Compatible "No", the products without the attribute of Multibrand Compatible no are displayed.

Let's say initially I click on Multibrand with the attribute of "No". If I then click on Tilt with the attribute of "Yes", the products that have a tilt, and are NOT Multibrand Compatible should be displayed.

Instead, I get all of the products with a tilt, including ones with a positive Multibrand Compatibility.

The URL Path is:

/?multibrand=no&tilt=yes 

Here is my HTML:

function multiplan_compatible_filter() {
    // Start output buffering to capture the HTML output
    ob_start();
    ?>
    <!-- HTML structure for the Multibrand filter dropdown -->
    <div id="multibrand-tilt-filter-container" class="multibrand-dropdown">
        <div class="multibrand-dropdown-wrapper">
            <button class="multibrand-dropdown-btn">Multibrand Compatible ▼</button>
            <div class="multibrand-dropdown-content">
                <label>
                    <input type="checkbox" id="multibrand-yes" value="yes" data-attribute="pa_multibrand-compatible">
                    Yes
                </label>
                <label>
                    <input type="checkbox" id="multibrand-no" value="no" data-attribute="pa_multibrand-compatible">
                    No
                </label>
            </div>
        </div>
    </div>

Here is the main Javascript and AJAX:

   <!-- JavaScript for handling the filter functionality -->
    <script>
    jQuery(function($) {
        // Toggle dropdown visibility when the button is clicked
        $('.multibrand-dropdown-btn').click(function(e) {
            e.stopPropagation();
            $('.multibrand-dropdown-content').toggleClass('show');
        });

        // Close dropdown when clicking outside
        $(window).click(function() {
            $('.multibrand-dropdown-content, .tilt-dropdown-content').removeClass('show');
        });

        // Handle checkbox changes
        $('#multibrand-yes, #multibrand-no').on('change', function() {
            const currentCheckbox = $(this);
            const isChecked = currentCheckbox.is(':checked');
            const isYesFilter = currentCheckbox.attr('id') === 'multibrand-yes';

            // Update URL parameter
            let currentUrl = new URL(window.location.href);
            let params = currentUrl.searchParams;
            params.delete('multibrand');
            if (isChecked) {
                params.set('multibrand', isYesFilter ? 'yes' : 'no');
            }
            history.pushState(null, '', '?' + params.toString());

            // Ensure only one checkbox is checked at a time
            if (isYesFilter) {
                $('#multibrand-no').prop('checked', false);
            } else {
                $('#multibrand-yes').prop('checked', false);
            }

            // Show loading overlay
            $('body').block({
                message: 'Updating products...',
                overlayCSS: { background: '#fff', opacity: 0.6 }
            });

            // Make AJAX request to update products
            $.ajax({
                url: wc_add_to_cart_params.ajax_url,
                type: 'POST',
                data: {
                    action: 'multibrand_compatible_products',
                    filter_yes: isYesFilter && isChecked,
                    filter_no: !isYesFilter && isChecked
                },
                success: function(response) {
                    if (response.success) {
                        // Update product display
                        $('.products').html(response.data.filtered_products);
                        if (response.data.other_products) {
                            $('.products').append('<h2>Other Products</h2>' + response.data.other_products);
                        }
                    }
                    $('body').unblock();
                },
                error: function() {
                    console.error('Failed to update products');
                    $('body').unblock();
                },
            });
        });

        // Initialize state on page load
        $(document).ready(function() {
            let params = new URLSearchParams(window.location.search);
            let multibrandValue = params.get('multibrand');

            // Reset checkboxes
            $('#multibrand-yes, #multibrand-no').prop('checked', false);

            // Set initial state based on URL parameter
            if (multibrandValue === 'yes') {
                $('#multibrand-yes').prop('checked', true);
            } else if (multibrandValue === 'no') {
                $('#multibrand-no').prop('checked', true);
            }

            // Trigger initial filter if needed
            if (multibrandValue) {
                $('#multibrand-yes, #multibrand-no').first(':checked').trigger('change');
            }
        });
    });
    </script>
    <?php
    // Return the captured HTML output
    return ob_get_clean();
}

// Register AJAX actions for both logged-in and non-logged-in users
add_action('wp_ajax_multibrand_compatible_products', 'multibrand_compatible_products');
add_action('wp_ajax_nopriv_multibrand_compatible_products', 'multibrand_compatible_products');

// AJAX handler for filtering products
function multibrand_compatible_products() {
    // Validate incoming data
    if (!isset($_POST['filter_yes']) || !isset($_POST['filter_no'])) {
        wp_send_json_error();
        return;
    }

    $filter_yes = $_POST['filter_yes'] === 'true';
    $filter_no = $_POST['filter_no'] === 'true';

    // Include the unified filter function
    require_once get_template_directory() . '/functions.php_FILES/Filter/unified_filter.php';

    // Apply the filter and get results
    $result = unified_product_filter('pa_multibrand-compatible', $filter_yes, $filter_no);

    // Send the filtered results back as a JSON response
    wp_send_json_success($result);
}

I made a unified Query System for the rest of my code so it is easier to scale:

<?php
function unified_product_filter($attribute, $filter_yes, $filter_no) {
    // Set up the base arguments for WP_Query to fetch products
    $filtered_args = array(
        'post_type' => 'product',
        'posts_per_page' => -1, // Retrieve all matching products
    );

    // Initialize the tax_query array for filtering by product attributes
    $filtered_args['tax_query'] = array(
        'relation' => 'AND', // All conditions in this array must be met
    );

    // Add attribute filters based on user selection
    if ($filter_yes) {
        // If 'Yes' is selected, add a filter for products with 'yes' value for the given attribute
        $filtered_args['tax_query'][] = array(
            'taxonomy' => $attribute, // The product attribute to filter by (e.g., 'pa_multibrand-compatible')
            'field' => 'slug',
            'terms' => 'yes',
        );
    } elseif ($filter_no) {
        // If 'No' is selected, add a filter for products with 'no' value for the given attribute
        $filtered_args['tax_query'][] = array(
            'taxonomy' => $attribute,
            'field' => 'slug',
            'terms' => 'no',
        );
    }

    // Perform the query to get filtered products
    $filtered_query = new WP_Query($filtered_args);

    // Initialize variables to store HTML output
    $filtered_products_html = '';
    $other_products_html = '';

    // Check if there are filtered products
    if ($filtered_query->have_posts()) {
        while ($filtered_query->have_posts()) {
            $filtered_query->the_post();
            // Generate HTML for each filtered product
            $filtered_products_html .= generate_product_html(get_the_ID());
        }
        wp_reset_postdata(); // Reset the global $post variable
    }

    // If no products match the filter, get all products
    if (empty($filtered_products_html)) {
        $all_products_query = new WP_Query(array(
            'post_type' => 'product',
            'posts_per_page' => -1,
        ));

        if ($all_products_query->have_posts()) {
            while ($all_products_query->have_posts()) {
                $all_products_query->the_post();
                // Generate HTML for each product
                $other_products_html .= generate_product_html(get_the_ID());
            }
            wp_reset_postdata();
        }
    }

    // Return the results as an array
    return array(
        'filtered_products' => $filtered_products_html,
        'other_products' => $other_products_html,
    );
}

// Helper function to generate HTML for a single product
function generate_product_html($product_id) {
    $product = wc_get_product($product_id);
    
    // Generate HTML for the product (customize as needed)
    $html = '<div class="product">';
    $html .= '<h3>' . $product->get_name() . '</h3>';
    $html .= '<p>Price: ' . $product->get_price_html() . '</p>';
    // Add more product details as needed
    $html .= '</div>';

    return $html;
}

I believe that anytime a new filter is added, whether through a click of a checkbox, or a selection of a dropdown, the on-loading query either over-writes everything else or only does a query for the toggled newest filter. Is there any way to have the filtering work on-page load for each seperate URL path of the filtered attribute? (ie: tilt=yes and multibrand=no display products with tilt of "yes" and multibrand of "no")

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.