How to Combine AJAX load more button with AJAX filters

As usual I work with one of the default WordPress themes – Twenty Seventeen, why not. I would like this post to be clear for you, so I split it into steps – Step 1 is all about HTML, Step 2,3 is all about JavaScript and Step 4 is all about PHP.

To understand how it works, watch this video:

Step 1. Creating «Posts per page» and «Order by» filters. Load more button.

First of all make sure that all the posts on the page are wrapped into the container, but the load more button and the filter form are outside of this container. Example:

<!-- Filters will be here -->
<div id="misha_posts_wrap">
	<!-- Posts will be here -->
</div>
<!-- Load more button will be here -->

Filters

I decided to make this code simple and use only two dropdown selects – for posts per page and for ordering posts. If you’re interested in sorting posts by meta values or by categories (taxonomy terms), I recommend you to look at this tutorial for more information.

<form id="misha_filters" action="#">
	<label for="misha_number_of_results">Per page</label>
	<select name="misha_number_of_results" id="misha_number_of_results">
		<option><?php echo get_option( 'posts_per_page' ) ?></option><!-- it is from Settings > Reading -->
		<option>5</option>
		<option>10</option>
		<option value="-1">All</option>
	</select>
				
	<label for="misha_order_by">Order</label>
	<select name="misha_order_by" id="misha_order_by">
		<option value="date-DESC">Date ↓</option><!-- I will explode these values by "-" symbol later -->
		<option value="date-ASC">Date ↑</option>
		<option value="comment_count-DESC">Comments ↓</option>
		<option value="comment_count-ASC">Comments ↑</option>
	</select>
				
	<!-- required hidden field for admin-ajax.php -->
	<input type="hidden" name="action" value="mishafilter" />
				
	<button>Apply Filters</button>
</form>

You can find some CSS styles in the child theme, link is under the video above.

Load more button

If the code below doesn’t work for you, try to add global $wp_query; before.

if ( $wp_query->max_num_pages > 1 ) :
	echo '<div id="misha_loadmore">More posts</div>'; // you can use <a> as well
endif;

In this case I do not recommend you to use another form of conditional statement because there could be conflicts in theme.

Step 2. Script connection, passing parameters to wp_localize_script()

wp_localize_script() function is the awesome thing that allows you not only to translate strings in your JavaScript code but also pass the dynamically generated parameters in it.

So, nothing especial below – we just use WordPress default wp_enqueue_scripts action hook to include our script.js and pass some parameters there (yes, create please script.js somewhere, I did it in the theme directory).

add_action( 'wp_enqueue_scripts', 'misha_script_and_styles');

function misha_script_and_styles() {
	// absolutely need it, because we will get $wp_query->query_vars and $wp_query->max_num_pages from it.
	global $wp_query;

	// when you use wp_localize_script(), do not enqueue the target script immediately
	wp_register_script( 'misha_scripts', get_stylesheet_directory_uri() . '/script.js', array('jquery') );
 
	// passing parameters here
	// actually the <script> tag will be created and the object "misha_loadmore_params" will be inside it 
	wp_localize_script( 'misha_scripts', 'misha_loadmore_params', array(
		'ajaxurl' => site_url() . '/wp-admin/admin-ajax.php', // WordPress AJAX
		'posts' => json_encode( $wp_query->query_vars ), // everything about your loop is here
		'current_page' => $wp_query->query_vars['paged'] ? $wp_query->query_vars['paged'] : 1,
		'max_page' => $wp_query->max_num_pages
	) );
 
 	wp_enqueue_script( 'misha_scripts' );
}

A little more description of this process you can find in the post about load more button.

Code to functions.php.

Step 3. The Load More and AJAX Filtering Script

It is much simpler than you think. Here it is:

jQuery(function($){
 
	/*
	 * Load More
	 */
	$('#misha_loadmore').click(function(){
 
		$.ajax({
			url : misha_loadmore_params.ajaxurl, // AJAX handler
			data : {
				'action': 'loadmorebutton', // the parameter for admin-ajax.php
				'query': misha_loadmore_params.posts, // loop parameters passed by wp_localize_script()
				'page' : misha_loadmore_params.current_page // current page
			},
			type : 'POST',
			beforeSend : function ( xhr ) {
				$('#misha_loadmore').text('Loading...'); // some type of preloader
			},
			success : function( posts ){
				if( posts ) {
 
					$('#misha_loadmore').text( 'More posts' );
					$('#misha_posts_wrap').append( posts ); // insert new posts
					misha_loadmore_params.current_page++;
 
					if ( misha_loadmore_params.current_page == misha_loadmore_params.max_page ) 
						$('#misha_loadmore').hide(); // if last page, HIDE the button
 
				} else {
					$('#misha_loadmore').hide(); // if no data, HIDE the button as well
				}
			}
		});
		return false;
	});
 
	/*
	 * Filter
	 */
	$('#misha_filters').submit(function(){
 
		$.ajax({
			url : misha_loadmore_params.ajaxurl,
			data : $('#misha_filters').serialize(), // form data
			dataType : 'json', // this data type allows us to receive objects from the server
			type : 'POST',
			beforeSend : function(xhr){
				$('#misha_filters').find('button').text('Filtering...');
			},
			success : function( data ){
 
				// when filter applied:
				// set the current page to 1
				misha_loadmore_params.current_page = 1;
 
				// set the new query parameters
				misha_loadmore_params.posts = data.posts;
 
				// set the new max page parameter
				misha_loadmore_params.max_page = data.max_page;
 
				// change the button label back
				$('#misha_filters').find('button').text('Apply filter');
 
				// insert the posts to the container
				$('#misha_posts_wrap').html(data.content);
 
				// hide load more button, if there are not enough posts for the second page
				if ( data.max_page < 2 ) {
					$('#misha_loadmore').hide();
				} else {
					$('#misha_loadmore').show();
				}
			}
		});
 
		// do not submit the form
		return false;
 
	});
 
});

At this moment if you try to use the filter form or the load more button your script should print 0, if not – you made a mistake somewhere.

Step 4. Process the AJAX Request in wp_ajax_ Action Hook

To the functions.php:

add_action('wp_ajax_loadmorebutton', 'misha_loadmore_ajax_handler');
add_action('wp_ajax_nopriv_loadmorebutton', 'misha_loadmore_ajax_handler');
 
function misha_loadmore_ajax_handler(){
 
	// prepare our arguments for the query
	$params = json_decode( stripslashes( $_POST['query'] ), true ); // query_posts() takes care of the necessary sanitization 
	$params['paged'] = $_POST['page'] + 1; // we need next page to be loaded
	$params['post_status'] = 'publish';
 
	// it is always better to use WP_Query but not here
	query_posts( $params );
 
	if( have_posts() ) :
 
		// run the loop
		while( have_posts() ): the_post();
 
			// look into your theme code how the posts are inserted, but you can use your own HTML of course
			// do you remember? - my example is adapted for Twenty Seventeen theme
			get_template_part( 'template-parts/post/content', get_post_format() );
			// for the test purposes comment the line above and uncomment the below one
			// the_title();
 
 
		endwhile;
	endif;
	die; // here we exit the script and even no wp_reset_query() required!
}
 
 
 
add_action('wp_ajax_mishafilter', 'misha_filter_function'); 
add_action('wp_ajax_nopriv_mishafilter', 'misha_filter_function');
 
function misha_filter_function(){
 
	// example: date-ASC 
	$order = explode( '-', $_POST['misha_order_by'] );
 
	$params = array(
		'posts_per_page' => $_POST['misha_number_of_results'], // when set to -1, it shows all posts
		'orderby' => $order[0], // example: date
		'order'	=> $order[1] // example: ASC
	);
 
 
	query_posts( $params );
 
	global $wp_query;
 
	if( have_posts() ) :
 
 		ob_start(); // start buffering because we do not need to print the posts now
 
		while( have_posts() ): the_post();
 
			// adapted for Twenty Seventeen theme
			get_template_part( 'template-parts/post/content', get_post_format() );
 
		endwhile;
 
 		$posts_html = ob_get_contents(); // we pass the posts to variable
   		ob_end_clean(); // clear the buffer
	else:
		$posts_html = '<p>Nothing found for your criteria.</p>';
	endif;
 
	// no wp_reset_query() required
 
 	echo json_encode( array(
		'posts' => json_encode( $wp_query->query_vars ),
		'max_page' => $wp_query->max_num_pages,
		'found_posts' => $wp_query->found_posts,
		'content' => $posts_html
	) );
 
	die();
}

And I want to remind you one more time – if code seems complicated for you, just download Twenty Seventeen child theme with the ready functionality, link is under the video at the beginning of this post.

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X