WordPress Front-end AJAX Pagination with Search and Sort
I posted a tutorial a year ago about creating an AJAX Pagination in your WordPress front-end, in this tutorial I would like to show how you can integrate a Search Filter and Sorting functionality. This tutorial also covers how you can go back to previous page state (pagination number and applied sorting filter) after visiting a post then clicking the back button in your browser.
Step 1: Create a custom page in WordPress
- Go to your Dashboard > Pages > Add New
- Name the page anything you want, ex: My Posts
- In your Dashboard > Settings > Permalinks, make sure Common Settings is set to Post Name
- In your newly created page, copy the page slag: if you used “My Posts” as your page name, the slag would be my-posts
- In your WordPress theme create a file called page-my-posts.php Notice how we attached the slag to the “page-“, this will allow us to add custom scripts that will only apply to this specific page.
- Go to your browser and navigate to your new page. ex. http://example.com/my-posts It should show you a blank white page, if not then you did something incorrectly.
Step 2: Working on our page
- Open the new page and paste the following code template:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | <?php get_header(); ?> <div class ="col-md-12 content"> <div class = "content"> <form class = "post-list"> <input type = "hidden" value = "" /> </form> <?php if ( have_posts() ) while ( have_posts() ) : the_post(); ?> <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>> <h1 class="entry-title"><?php the_title(); ?></h1> <hr /> <article class="entry-content clear"> <?php the_content(); ?> </article> </article> <?php endwhile; ?> <article class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control post_search_text" placeholder="Enter a keyword"> </div> <input type = "submit" value = "Search" class = "btn btn-success post_search_submit" /> </article> <br class = "clear" /> <script type="text/javascript"> var ajaxurl = '<?php echo admin_url('admin-ajax.php'); ?>'; function cvf_load_all_posts(page, th_name, th_sort){ $(".cvf_universal_container").html('<p><img src = "<?php bloginfo('template_url'); ?>/images/loading.gif" class = "loader" /></p>'); var post_data = { page: page, search: $('.post_search_text').val(), th_name: th_name, th_sort: th_sort }; $('form.post-list input').val(JSON.stringify(post_data)); var data = { action: "demo_load_my_posts", data: JSON.parse($('form.post-list input').val()) }; $.post(ajaxurl, data, function(response) { if($(".cvf_universal_container").html(response)){ $('.table-post-list th').each(function() { // Append the button indicator $(this).find('span.glyphicon').remove(); if($(this).hasClass('active')){ if(JSON.parse($('form.post-list input').val()).th_sort == 'DESC'){ $(this).append(' <span class="glyphicon glyphicon-chevron-down"></span>'); } else { $(this).append(' <span class="glyphicon glyphicon-chevron-up"></span>'); } } }); } }); } jQuery(document).ready(function($) { // Initialize default item to sort and it's sort order // Check if our hidden form input is not empty, meaning it's not the first time viewing the page. if($('form.post-list input').val()){ // Submit hidden form input value to load previous page number data = JSON.parse($('form.post-list input').val()); cvf_load_all_posts(data.page, data.th_name, data.th_sort); } else { // Load first page cvf_load_all_posts(1, 'post_title', 'ASC'); } var th_active = $('.table-post-list th.active'); var th_name = $(th_active).attr('id'); var th_sort = $(th_active).hasClass('DESC') ? 'ASC': 'DESC'; // Search $('body').on('click', '.post_search_submit', function(){ cvf_load_all_posts(1, th_name, th_sort); }); // Search when Enter Key is triggered $(".post_search_text").keyup(function (e) { if (e.keyCode == 13) { cvf_load_all_posts(1, th_name, th_sort); } }); // Pagination Clicks $('.cvf_universal_container .cvf-universal-pagination li.active').live('click',function(){ var page = $(this).attr('p'); var current_sort = $(th_active).hasClass('DESC') ? 'DESC': 'ASC'; cvf_load_all_posts(page, th_name, current_sort); }); // Sorting Clicks $('body').on('click', '.table-post-list th', function(e) { e.preventDefault(); var th_name = $(this).attr('id'); if(th_name){ // Remove all TH tags with an "active" class if($('.table-post-list th').removeClass('active')) { // Set "active" class to the clicked TH tag $(this).addClass('active'); } if(!$(this).hasClass('DESC')){ cvf_load_all_posts(1, th_name, 'DESC'); $(this).addClass('DESC'); } else { cvf_load_all_posts(1, th_name, 'ASC'); $(this).removeClass('DESC'); } } }) }); </script> <table class = "table table-striped table-post-list no-margin"> <tr> <th width = "25%" class = "active" id = "post_title"><u><a href = "#">Post Name</a></u></th> <th width = "60%">Description</th> <th width = "15%" id = "post_date"><u><a href = "#">Post Date</a></u></th> </tr> </table> <div class = "cvf_pag_loading no-padding"> <div class = "cvf_universal_container"> <div class="cvf-universal-content"></div> </div> </div> </div> </div> <?php get_footer(); ?> |
- Explanations are on the codes
- This tutorial make use of bootstrap classes, so I suggest including bootstrap on the header part of your website:
1 | <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" /> |
Step 3: Add the following PHP Code to your functions.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | add_action( 'wp_ajax_demo_load_my_posts', 'demo_load_my_posts' ); add_action( 'wp_ajax_nopriv_demo_load_my_posts', 'demo_load_my_posts' ); function demo_load_my_posts() { global $wpdb; $msg = ''; if( isset( $_POST['data']['page'] ) ){ // Always sanitize the posted fields to avoid SQL injections $page = sanitize_text_field($_POST['data']['page']); // The page we are currently at $name = sanitize_text_field($_POST['data']['th_name']); // The name of the column name we want to sort $sort = sanitize_text_field($_POST['data']['th_sort']); // The order of our sort (DESC or ASC) $cur_page = $page; $page -= 1; $per_page = 15; // Number of items to display per page $previous_btn = true; $next_btn = true; $first_btn = true; $last_btn = true; $start = $page * $per_page; // The table we are querying from $posts = $wpdb->prefix . "posts"; $where_search = ''; // Check if there is a string inputted on the search box if( ! empty( $_POST['data']['search']) ){ // If a string is inputted, include an additional query logic to our main query to filter the results $where_search = ' AND (post_title LIKE "%%' . $_POST['data']['search'] . '%%" OR post_content LIKE "%%' . $_POST['data']['search'] . '%%") '; } // Retrieve all the posts $all_posts = $wpdb->get_results($wpdb->prepare(" SELECT * FROM $posts WHERE post_type = 'post' AND post_status = 'publish' $where_search ORDER BY $name $sort LIMIT %d, %d", $start, $per_page ) ); $count = $wpdb->get_var($wpdb->prepare(" SELECT COUNT(ID) FROM " . $posts . " WHERE post_type = 'post' AND post_status = 'publish' $where_search", array() ) ); // Check if our query returns anything. if( $all_posts ): $msg .= '<table class = "table table-striped table-hover table-file-list">'; // Iterate thru each item foreach( $all_posts as $key => $post ): $msg .= ' <tr> <td width = "25%"><a href = "' . get_permalink( $post->ID ) . '">' . $post->post_title . '</a></td> <td width = "60%">' . $post->post_excerpt . '</td> <td width = "15%">' . $post->post_date . '</td> </tr>'; endforeach; $msg .= '</table>'; // If the query returns nothing, we throw an error message else: $msg .= '<p class = "bg-danger">No posts matching your search criteria were found.</p>'; endif; $msg = "<div class='cvf-universal-content'>" . $msg . "</div><br class = 'clear' />"; $no_of_paginations = ceil($count / $per_page); if ($cur_page >= 7) { $start_loop = $cur_page - 3; if ($no_of_paginations > $cur_page + 3) $end_loop = $cur_page + 3; else if ($cur_page <= $no_of_paginations && $cur_page > $no_of_paginations - 6) { $start_loop = $no_of_paginations - 6; $end_loop = $no_of_paginations; } else { $end_loop = $no_of_paginations; } } else { $start_loop = 1; if ($no_of_paginations > 7) $end_loop = 7; else $end_loop = $no_of_paginations; } $pag_container .= " <div class='cvf-universal-pagination'> <ul>"; if ($first_btn && $cur_page > 1) { $pag_container .= "<li p='1' class='active'>First</li>"; } else if ($first_btn) { $pag_container .= "<li p='1' class='inactive'>First</li>"; } if ($previous_btn && $cur_page > 1) { $pre = $cur_page - 1; $pag_container .= "<li p='$pre' class='active'>Previous</li>"; } else if ($previous_btn) { $pag_container .= "<li class='inactive'>Previous</li>"; } for ($i = $start_loop; $i <= $end_loop; $i++) { if ($cur_page == $i) $pag_container .= "<li p='$i' class = 'selected' >{$i}</li>"; else $pag_container .= "<li p='$i' class='active'>{$i}</li>"; } if ($next_btn && $cur_page < $no_of_paginations) { $nex = $cur_page + 1; $pag_container .= "<li p='$nex' class='active'>Next</li>"; } else if ($next_btn) { $pag_container .= "<li class='inactive'>Next</li>"; } if ($last_btn && $cur_page < $no_of_paginations) { $pag_container .= "<li p='$no_of_paginations' class='active'>Last</li>"; } else if ($last_btn) { $pag_container .= "<li p='$no_of_paginations' class='inactive'>Last</li>"; } $pag_container = $pag_container . " </ul> </div>"; echo '<div class = "cvf-pagination-content">' . $msg . '</div>' . '<div class = "cvf-pagination-nav">' . $pag_container . '</div>'; } exit(); } |
Step 4: Add some CSS styling to our Pagination navigation:
- Include the following code in your style.css:
1 2 3 4 5 6 | .cvf_pag_loading {padding: 20px; } .cvf-universal-pagination ul {margin: 0; padding: 0;} .cvf-universal-pagination ul li {display: inline; margin: 3px; padding: 4px 8px; background: #FFF; color: black; } .cvf-universal-pagination ul li.active:hover {cursor: pointer; background: #1E8CBE; color: white; } .cvf-universal-pagination ul li.inactive {background: #7E7E7E;} .cvf-universal-pagination ul li.selected {background: #1E8CBE; color: white;} |
Do you need help with a project? or have a new project in mind that you need help with?
Contact Me