The filtering and sorting seem to work fine, however on initial page load, and when attempting to filter/sort through the items only half of the posts are being displayed, 70, out of 140.
The values such as _discount_price or percentage are available in the DB for all the posts, on some of the posts product_categories are not assigned, however I don’t think that this is causing the posts not to load.
I’ve been at this problem for couple of days and I cannot pinpoint where I am making a mistake. Any suggestion/assistance would be welcomed.
This is the form
<form action="<?php echo home_url('/'); ?>" method="get" class="searchandfilter">
<div class="search-form">
<input type="text" name="s" placeholder="Search..." class="search-input">
</div>
<button class="filterButton" type="button"><span>Filter & Sort</span></button>
<div id="discount-filter" class="hidden">
<h2 class="form-title">Percentage Discount</h2>
<input type="range" id="discountRange" name="discountRange" min="0" max="100" value="20" class="slider">
<span id="discountValue" aria-live="assertive">20%</span>
</div>
<div id="status-filter" class="hidden">
<h2 class="form-title">Status</h2>
<div class="input-group">
<input type="checkbox" name="status[]" value="active-deals" id="statusActive" class="styled-checkbox"><label for="statusActive">Active</label>
</div>
<div class="input-group">
<input type="checkbox" name="status[]" value="archived-deals" id="statusArchived" class="styled-checkbox"><label for="statusArchived">Archived</label>
</div>
</div>
<div id="categories-filter" class="hidden">
<h2 class="form-title">Categories</h2>
<div class="category-list">
<?= display_categories_recursive(0); ?>
</div>
<button id="see-more-categories" type="button" class="see-more" data-expanded="false">See More...</button>
</div>
<div id="stores-filter" class="hidden">
<h2 class="form-title">Stores</h2>
<?php
$store_terms = get_terms('store_type', array('hide_empty' => false));
foreach ($store_terms as $term) {
echo '<div class="input-group">';
echo '<input type="checkbox" name="store[]" value="' . esc_attr($term->slug) . '" id="store' . esc_attr($term->term_id) . '" class="styled-checkbox"><label for="store' . esc_attr($term->term_id) . '">' . esc_html($term->name) . '</label>';
echo '</div>';
}
?>
</div>
<div id="posts-per-page-filter" class="hidden">
<h2 class="form-title">Posts Per Page</h2>
<select id="postsPerPage" name="postsPerPage" class="selector">
<option value="10">10</option>
<option value="30">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
<div id="sort-filter" class="hidden">
<div class="input-group">
<h2 class="form-title">Sort By</h2>
<select id="sortOrder" name="sortOrder" class="selector">
<option value="price-low-to-high">Price: Low to High</option>
<option value="price-high-to-low">Price: High to Low</option>
<option value="discount-low-to-high">Discount: Low to High</option>
<option value="discount-high-to-low">Discount: High to Low</option>
<option value="date-newest" selected>Date: Newest</option>
<option value="date-oldest">Date: Oldest</option>
</select>
</div>
</div>
</form>
This below is the query builder:
function build_query_args($params, $is_initial_load = false) {
$args = array(
'post_type' => 'post',
'posts_per_page' => isset($params['postsPerPage']) ? intval($params['postsPerPage']) : 10,
'paged' => isset($params['page']) ? intval($params['page']) : 1,
);
if (!empty($params['s'])) {
$args['s'] = sanitize_text_field($params['s']);
}
$tax_query = array('relation' => 'AND');
if (!empty($params['category'])) {
$tax_query[] = array(
'taxonomy' => 'product_categories',
'field' => 'slug',
'terms' => $params['category'],
'operator' => 'IN'
);
}
if ($is_initial_load || empty($params['status'])) {
$tax_query[] = array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => 'active-deals',
);
} elseif (!empty($params['status'])) {
$tax_query[] = array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => $params['status'],
);
}
if (!empty($params['store'])) {
$tax_query[] = array(
'taxonomy' => 'store_type',
'field' => 'slug',
'terms' => $params['store'],
);
}
if (!empty($tax_query)) {
$args['tax_query'] = $tax_query;
}
$discount_percentage = $is_initial_load ? 0 : (isset($params['discount']) ? intval($params['discount']) : 0);
$args['meta_query'] = array(
array(
'key' => '_discount_percentage',
'value' => $discount_percentage,
'type' => 'NUMERIC',
'compare' => '>=',
)
);
if (!empty($params['sortOrder'])) {
switch ($params['sortOrder']) {
case 'price-low-to-high':
$args['meta_key'] = '_discount_price';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'ASC';
break;
case 'price-high-to-low':
$args['meta_key'] = '_discount_price';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
case 'discount-low-to-high':
$args['meta_key'] = '_discount_percentage';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'ASC';
break;
case 'discount-high-to-low':
$args['meta_key'] = '_discount_percentage';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
case 'date-newest':
$args['orderby'] = 'date';
$args['order'] = 'DESC';
break;
case 'date-oldest':
$args['orderby'] = 'date';
$args['order'] = 'ASC';
break;
}
}
return $args;
}
the processing method:
function ajax_filter_posts_handler() {
check_ajax_referer('filter_posts_nonce', 'nonce');
$is_initial_load = isset($_POST['initial_load']) && $_POST['initial_load'] === 'true';
$args = build_query_args($_POST, $is_initial_load);
$query = new WP_Query($args);
ob_start();
if ($query->have_posts()) {
while ($query->have_posts()) {
echo do_shortcode('[elementor-template id="71260"]');
}
} else {
error_log('No posts found');
echo '<h2>No posts found.</h2>';
}
$content = ob_get_clean();
$posts_per_page = $args['posts_per_page'];
$total_posts = $query->found_posts;
$total_pages = ceil($total_posts / $posts_per_page);
$pagination = generate_custom_pagination($args['paged'], $total_pages);
wp_reset_postdata();
$response = array(
'content' => $content,
'pagination' => $pagination,
'total_posts' => $total_posts,
'total_pages' => $total_pages,
'current_page' => $args['paged'],
'posts_per_page' => $posts_per_page
);
wp_send_json_success($response);
}
and the AJAX call…
function filterPosts(page = 1) {
let status = $('[name="status[]"]:checked').map(function() { return $(this).val(); }).get();
if (status.length === 0 && isInitialLoad) {
status = ['active-deals'];
$('#statusActive').prop('checked', true);
}
let data = {
'action': 'filter_posts',
'nonce': ajax_object.nonce,
's': $('.search-input').val(),
'category': $('[name="category[]"]:checked').map(function() { return $(this).val(); }).get(),
'status': status,
'store': $('[name="store[]"]:checked').map(function() { return $(this).val(); }).get(),
'discount': $('#discountRange').val(),
'sortOrder': $('#sortOrder').val(),
'postsPerPage': $('#postsPerPage').val(),
'page': page,
'initial_load': isInitialLoad
};
$.ajax({
url: ajax_object.ajax_url,
type: 'POST',
data: data,
success: function(response) {
console.log('AJAX response:', response);
if (response.success) {
$('.elementor-loop-container').html(response.data.content);
updatePagination(response.data.pagination);
updateTotalPosts(response.data.total_posts, response.data.total_pages, page);
$('html, body').animate({
scrollTop: $('.elementor-loop-container').offset().top
}, 500);
updateUrlParameter('paged', page);
} else {
console.error('Error in AJAX response');
}
},
error: function(xhr, status, error) {
console.error('AJAX error:', error);
},
complete: function() {
isInitialLoad = false;
}
});
}
function updatePagination(paginationHtml) {
$('.elementor-pagination').html(paginationHtml);
}
function updateTotalPosts(totalPosts, totalPages, currentPage) {
$('.total-posts').text(totalPosts);
$('.total-pages').text(totalPages);
$('.current-page').text(currentPage);
}
function updateUrlParameter(key, value) {
var url = window.location.href;
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
var separator = url.indexOf('?') !== -1 ? "&" : "?";
if (url.match(re)) {
url = url.replace(re, '$1' + key + "=" + value + '$2');
}
else {
url = url + separator + key + "=" + value;
}
url = url.replace(/&e-page-[^=]+=[^&]*/g, '');
window.history.pushState(null, null, url);
}
$('.searchandfilter input, .searchandfilter select').on('change', function() {
filterPosts(1);
});
$('#postsPerPage').on('change', function() {
filterPosts(1);
});
$('#sortOrder').on('change', function() {
filterPosts(1);
});
$(document).on('click', '.elementor-pagination a, .page-numbers a', function(e) {
e.preventDefault();
let currentPage = parseInt($('.current-page').text()) || 1;
let page;
if ($(this).hasClass('prev') || $(this).text().includes('Previous')) {
page = currentPage - 1;
} else if ($(this).hasClass('next') || $(this).text().includes('Next')) {
page = currentPage + 1;
} else {
page = parseInt($(this).text());
}
if (isNaN(page) || page < 1) {
page = 1;
}
filterPosts(page);
});
filterPosts(1);
}
$(document).ready(function() {
if ($('body').hasClass('category-2') || $('body').hasClass('page-id-161') || $('body').hasClass('category-515')) {
initializeFiltering();
}
});
8