如何为WooCommerce Ajax搜索生成按文章类型过滤的URL?
Hey there! Let's figure out how to get your WooCommerce typeahead search showing separated product and content results properly. The issue you're facing is that your existing AJAX endpoint isn't respecting the posttype parameter—so we'll need to adjust the backend logic first, then set up the frontend to either fetch separate results or group them in a single request.
Step 1: Update the AJAX Search Handler
First, let's modify the backend function that handles your ajax_search action. The goal is to make it recognize the posttype parameter and filter results accordingly, or return grouped results for all post types if the parameter isn't set.
Add this to your theme's functions.php file (or a custom plugin):
add_action('wp_ajax_ajax_search', 'custom_filtered_ajax_search'); add_action('wp_ajax_nopriv_ajax_search', 'custom_filtered_ajax_search'); function custom_filtered_ajax_search() { // Validate search term exists if (!isset($_GET['terms']) || empty($_GET['terms'])) { wp_send_json_error('No search term provided'); } $search_term = sanitize_text_field($_GET['terms']); // Determine which post types to search $target_post_types = isset($_GET['posttype']) ? (array) $_GET['posttype'] : ['post', 'product', 'page']; // Default to all three types // Set up WP_Query arguments $query_args = [ 's' => $search_term, 'post_type' => $target_post_types, 'posts_per_page' => 8, // Adjust based on how many results you want 'post_status' => 'publish', 'no_found_rows' => true, // Optimize for faster queries ]; $search_query = new WP_Query($query_args); $results = []; if ($search_query->have_posts()) { while ($search_query->have_posts()) { $search_query->the_post(); $post_type = get_post_type(); // Group results by post type if (!isset($results[$post_type])) { $results[$post_type] = []; } // Add relevant data (customize this based on your needs) $results[$post_type][] = [ 'id' => get_the_ID(), 'title' => get_the_title(), 'url' => get_permalink(), 'price' => $post_type === 'product' ? wc_get_price_to_display(get_post()) : '', 'excerpt' => wp_trim_words(get_the_excerpt(), 10) ]; } wp_reset_postdata(); } wp_send_json_success($results); }
This function will now:
- Respect the
posttypeparameter (you can pass a single type likeproductor multiple types likepost,page) - Return results grouped by post type if no
posttypeis specified - Include WooCommerce-specific data like product prices for product results
Step 2: Frontend Options for Separated Results
You have two solid options here—choose whichever fits your workflow better:
Option 1: Separate AJAX Requests for Each Post Type
If you want to fetch results for each post type independently (e.g., one request for products, one for posts), use these URLs in your typeahead configuration:
- Products only:
wp-admin/admin-ajax.php?action=ajax_search&terms=[searchterm]&posttype=product - Posts only:
wp-admin/admin-ajax.php?action=ajax_search&terms=[searchterm]&posttype=post - Pages only:
wp-admin/admin-ajax.php?action=ajax_search&terms=[searchterm]&posttype=page
Here's how to set this up with Typeahead.js (using Bloodhound for remote data):
// Initialize Bloodhound for products const productSuggestions = new Bloodhound({ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title'), queryTokenizer: Bloodhound.tokenizers.whitespace, remote: { url: 'wp-admin/admin-ajax.php?action=ajax_search&terms=%QUERY&posttype=product', wildcard: '%QUERY', transform: (response) => response.success ? response.data.product : [] } }); // Initialize Bloodhound for posts const postSuggestions = new Bloodhound({ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title'), queryTokenizer: Bloodhound.tokenizers.whitespace, remote: { url: 'wp-admin/admin-ajax.php?action=ajax_search&terms=%QUERY&posttype=post', wildcard: '%QUERY', transform: (response) => response.success ? response.data.post : [] } }); // Initialize Typeahead with separate result groups $('.search-input').typeahead({ hint: true, highlight: true, minLength: 2 }, { name: 'products', display: 'title', source: productSuggestions, templates: { header: '<h4 class="search-group-header">Products</h4>', suggestion: (item) => ` <div class="search-result product"> <a href="${item.url}">${item.title}</a> <span class="product-price">${item.price}</span> </div> ` } }, { name: 'posts', display: 'title', source: postSuggestions, templates: { header: '<h4 class="search-group-header">Articles</h4>', suggestion: (item) => ` <div class="search-result post"> <a href="${item.url}">${item.title}</a> <p class="excerpt">${item.excerpt}</p> </div> ` } });
Option 2: Single Request with Grouped Results (Recommended)
For better performance (fewer HTTP requests), fetch all results in one call and let the frontend render them in separate groups. Here's how:
const allSearchResults = new Bloodhound({ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title'), queryTokenizer: Bloodhound.tokenizers.whitespace, remote: { url: 'wp-admin/admin-ajax.php?action=ajax_search&terms=%QUERY', wildcard: '%QUERY', transform: (response) => { if (!response.success) return []; // Flatten grouped results into a single array with headers const flattenedResults = []; const results = response.data; // Add product group if (results.product) { flattenedResults.push({ type: 'header', label: 'Products' }); flattenedResults.push(...results.product.map(item => ({ ...item, type: 'product' }))); } // Add post group if (results.post) { flattenedResults.push({ type: 'header', label: 'Articles' }); flattenedResults.push(...results.post.map(item => ({ ...item, type: 'post' }))); } // Add page group if (results.page) { flattenedResults.push({ type: 'header', label: 'Pages' }); flattenedResults.push(...results.page.map(item => ({ ...item, type: 'page' }))); } return flattenedResults; } } }); $('.search-input').typeahead({ hint: true, highlight: true, minLength: 2 }, { name: 'all-results', display: (item) => item.type === 'header' ? item.label : item.title, source: allSearchResults, templates: { suggestion: (item) => { if (item.type === 'header') { return `<h4 class="search-group-header">${item.label}</h4>`; } else if (item.type === 'product') { return ` <div class="search-result product"> <a href="${item.url}">${item.title}</a> <span class="product-price">${item.price}</span> </div> `; } else { return ` <div class="search-result content"> <a href="${item.url}">${item.title}</a> <p class="excerpt">${item.excerpt}</p> </div> `; } } } });
Why Your Original Parameter Didn't Work
The most likely reason the posttype parameter was ignored is that your original get_ajax_search function wasn't designed to read or act on that parameter. The updated function above explicitly checks for it and adjusts the WP_Query accordingly.
内容的提问来源于stack exchange,提问作者ThomasB

