Support » Developing with WordPress » Ajax Load More on Hierarchical Taxonomy Terms

  • Resolved saurav.rox

    (@sauravrox)


    Hi,
    I am developing a plugin where I have to add Ajax Load more on posts. The page where I need it is Taxonomy Archive Page.
    Everything works fine if there is no child categories but whenever child category is added then the ajax load more doesn’t work on child category. I mean the pagination of child category does not work independently. It depends on the parent category’s pagination. I have reset the query but that does not help at all.
    Below is the structure I am talking about:

    <strong>Parent category</strong>
    <em>...Post list...</em>
    <a href="#">Load More</a>
    <strong>Child category</strong>
    <em>...Post list...</em>
    <a href="#">Load More</a>

    It will be helpful if anyone shade light on this 🙂 Thank You!

Viewing 9 replies - 1 through 9 (of 9 total)
  • Moderator bcworkz

    (@bcworkz)

    Without seeing the queries run, it’s hard to say where the difficulty lies. Your jQuery needs to keep track of what the next offset argument will be for each term list. The query for each list’s load more request must use exactly the same arguments for every page, except for the offset argument. It’s the only variable for any given list. As long as that is done, the load more scripts should work correctly.

    Usually where pagination runs afoul is when arguments from one query are used to specify a different query, usually because the query vars passed are thought to be useful. While that may be the case, getting the values from a different query is fraught with problems. We should directly get values from the request and manage our own queries, not try to borrow from other queries.

    Would you please take a look at my code? Thank you!

    function wpte_be_load_more_js() {
            		global $wp_query;
            		if(!isset(get_queried_object()->slug))
            			return;
                    $wte_trip_cat_slug = get_queried_object()->slug;
            
            		if ( isset($_POST['second_class']) && $_POST['second_class']!= $wte_trip_cat_slug )
            		{
            			$wte_trip_cat_slug = $_POST['second_class'];
            		}
            
            		$args = array(
            			'nonce' => wp_create_nonce( 'be-load-more-nonce' ),
            			'url'   => admin_url( 'admin-ajax.php' ),
            			'query' => $wp_query->query,
            			'slug'  => $wte_trip_cat_slug,
            			'current_page' => isset($_POST['page']) ? esc_attr($_POST['page']) : 1,
            			'max_page' => $wp_query->max_num_pages
            		);
            				
            		wp_enqueue_script( 'be-load-more', plugin_dir_url( __FILE__ ) . 'js/load-more.js', array( 'jquery' ), '1.0', true );
            		wp_localize_script( 'be-load-more', 'beloadmore', $args );
            	}
            	/**
            	 * AJAX Load More 
            	 *
            	 */
            	function wpte_ajax_load_more() {
            		check_ajax_referer( 'be-load-more-nonce', 'nonce' );
            		$wp_travel_engine_setting_option_setting = get_option( 'wp_travel_engine_settings', true );					
            		$keys=array_keys($_POST['query']);
            		$args = 
            		array(
                        'post_type' => 'trip', // Your Post type Name that You Registered
                        'order' => 'ASC',
                        'tax_query' => array(
                            array(
                                'taxonomy' => $keys[0],
                                'field' => 'slug',
                                'terms' => $_POST['second_class'],
                                'include_children' => true
                            )
                        ),
                        'paged' => $_POST['page']
                    );
            		ob_start();
            		$loop = new WP_Query( $args );
            		if( $loop->have_posts() ): while( $loop->have_posts() ): $loop->the_post(); 
            			global $post;
                        $wp_travel_engine_setting = get_post_meta( $post->ID,'wp_travel_engine_setting',true );?>
            			<div class="col">
                            <div class="img-holder">
                                <a href="<?php echo esc_url( get_the_permalink() );?>" class="trip-post-thumbnail"><?php
            	                    $trip_feat_img_size = apply_filters('wp_travel_engine_archive_trip_feat_img_size','destination-thumb-trip-size');
            	                    $feat_image_url = wp_get_attachment_image_src( get_post_thumbnail_id($post->ID), $trip_feat_img_size );
                                    if(isset($feat_image_url[0]))
                                    { ?>
                                        <img src="<?php echo esc_url( $feat_image_url[0] );?>">
                                    <?php
                                    }
                                    else{
                                       echo '<img src="'.esc_url(  WP_TRAVEL_ENGINE_IMG_URL . '/public/css/images/trip-listing-fallback.jpg' ).'">';
                                    }?>
                                </a>
                                <?php
                                $code = 'USD';
                                if( isset($wp_travel_engine_setting_option_setting['currency_code']) && $wp_travel_engine_setting_option_setting['currency_code']!='')
                                {
                                    $code = esc_attr( $wp_travel_engine_setting_option_setting['currency_code'] );
                                }
                                $obj = new Wp_Travel_Engine_Functions();
                                $currency = $obj->wp_travel_engine_currencies_symbol( $code );
                                $cost = isset( $wp_travel_engine_setting['trip_price'] ) ? $wp_travel_engine_setting['trip_price']: '';
                                
                                $prev_cost = isset( $wp_travel_engine_setting['trip_prev_price'] ) ? $wp_travel_engine_setting['trip_prev_price']: '';
            
                                    $code = 'USD';
                                    if( isset( $wp_travel_engine_setting_option_setting['currency_code'] ) && $wp_travel_engine_setting_option_setting['currency_code']!= '' )
                                    {
                                        $code = $wp_travel_engine_setting_option_setting['currency_code'];
                                    } 
                                    $obj = new Wp_Travel_Engine_Functions();
                                    $currency = $obj->wp_travel_engine_currencies_symbol( $code );
                                    $prev_cost = isset($wp_travel_engine_setting['trip_prev_price']) ? $wp_travel_engine_setting['trip_prev_price']: '';
                                    if( $cost!='' && isset($wp_travel_engine_setting['sale']) )
                                    {
                                        $obj = new Wp_Travel_Engine_Functions();
                                        echo '<span class="price-holder"><span>'.esc_attr($currency).esc_attr( $obj->wp_travel_engine_price_format($cost) ).'</span></span>';
                                    }
                                    else{ 
                                        $obj = new Wp_Travel_Engine_Functions();
                                        echo '<span class="price-holder"><span>'.esc_attr($currency).esc_attr( $obj->wp_travel_engine_price_format($cost) ).'</span></span>';
                                    }
                                    ?>
                                </strong>
                            </div>
                            <div class="text-holder">
                                <h3 class="title"><a href="<?php echo esc_url( get_the_permalink() );?>"><?php the_title();?></a></h3>
                                <?php
                                $nonce = wp_create_nonce( 'wp-travel-engine-nonce' );
                                ?>
                                <?php
                                if( isset( $wp_travel_engine_setting['trip_duration'] ) && $wp_travel_engine_setting['trip_duration']!='' )
                                { ?>
                                    <div class="meta-info">
                                        <span class="time">
                                            <i class="fa fa-clock-o"></i>
                                            <?php echo esc_attr($wp_travel_engine_setting['trip_duration']); if($wp_travel_engine_setting['trip_duration']>1){ _e(' days','wp-travel-engine');} else{ _e(' day','wp-travel-engine'); }
                                            ?>
                                        </span>
                                    </div>
                                <?php } ?>
                                <div class="btn-holder">
                                    <a href="<?php echo esc_url( get_the_permalink() );?>" class="btn-more"><?php _e('View Detail','wp-travel-engine');?></a>
                                </div>
                            </div>
                        </div>
                    <?php
            		endwhile; 
            		wp_reset_postdata();
            		endif;
            		wp_reset_query(); 
            		$data = ob_get_clean();
            		wp_send_json_success( $data );
            		exit;
            	}
        		$this->loader->add_action( 'wp_ajax_wpte_ajax_load_more', $plugin_public, 'wpte_be_load_more_js' );
        		$this->loader->add_action( 'wp_ajax_nopriv_wpte_ajax_load_more', $plugin_public, 'wpte_be_load_more_js' );
        
        		$this->loader->add_action( 'wp_ajax_wpte_ajax_load_more', $plugin_public, 'wpte_ajax_load_more' );
        		$this->loader->add_action( 'wp_ajax_nopriv_wpte_ajax_load_more', $plugin_public, 'wpte_ajax_load_more' );

    And here is my javascript:

        jQuery(document).ready(function($){
        	var loading = false;
            $('body').on('click', '.btn-loadmore', function (e){ 
            	var second_class = $(this).parent().attr('class').split(' ')[1];
        		loading = true;
        		// beloadmore.current_page++;
        		var data = {
        			action: 'wpte_ajax_load_more',
        			nonce: beloadmore.nonce,
        			page: beloadmore.current_page,
        			query: beloadmore.query,
        			second_class : second_class
        		};
        		$.post(beloadmore.url, data, function(res) {
        			if( res.success) {
        				$('.'+data.second_class+' .btn-loadmore').before( res.data );
        				beloadmore.current_page++;
        				if( beloadmore.current_page == beloadmore.max_page  )
        				{
        					$('.'+data.second_class+' .btn-loadmore').remove();
        				}
        			} 
        			else {
        					$('.'+data.second_class+' .btn-loadmore').remove();
        			}
        		}).fail(function(xhr, textStatus, e) {
        			return false;
        		});
        	});
        });
    Moderator bcworkz

    (@bcworkz)

    I’m suspicious of the beloadmore object, particularly the .currentpage property. As I mentioned, each category list has to maintain its own current page. bloadmore appears to be related to the overall page, as it is passed through localize script. It’s fine at first because every list is on the first page of data. As more pages are loaded, it gets incremented regardless of what category was loaded. Things get quickly out of sync.

    The current page of each category needs to be managed independently. It could be some kind of global array, keyed by category term, or a non-visible value on each list, accessed through this clicked element. Passing this value as page: will get the proper content returned. Be sure to also increment the correct value upon success, or you’ll have the same out of sync problem.

    FYI, if you call wp_reset_query(), there’s no need for calling wp_reset_postdata() too, since that function is called within wp_reset_query(). The only difference between the two is wp_reset_query() also resets the main query in the internal $GLOBALS array. wp_reset_postdata() does not. There’s no harm in doing so, but it’s not necessary.

    In fact, if your page template will no longer be making use of the main query, there is nothing to be gained by calling either function. Your code does not need it because it’s creating a new query each time. As an Ajax handler, there is not even a main query to reset back to. But as I said, doing so does no harm, so when in doubt… 🙂

    Hello,
    Thank you for pointing me in right direction. I have understood what you are trying to suggest me. So far you have helped me a lot. But now I want some more help from you 🙂 . Okey, so I tried running the pagination independently but couldn’t get it done properly.
    Can I run multiple functions as a callback on WordPress ajax like this? Or, how can I localize the variables dynamically so that I can control the page argument?

    $this->loader->add_action( 'wp_ajax_wpte_ajax_load_more', $plugin_public, 'wpte_be_load_more_js' );
     $this->loader->add_action( 'wp_ajax_nopriv_wpte_ajax_load_more', $plugin_public, 'wpte_be_load_more_js' );
    $this->loader->add_action( 'wp_ajax_wpte_ajax_load_more', $plugin_public, 'wpte_ajax_load_more' );
    $this->loader->add_action( 'wp_ajax_nopriv_wpte_ajax_load_more', $plugin_public, 'wpte_ajax_load_more' );

    I also tried to save page for each of the terms in session but couldn’t get it done.
    Would you please help me some more? It would be really appreciable.

    Thank you so much!

    Moderator bcworkz

    (@bcworkz)

    I will help as much as I’m able to, but I don’t want this to turn into a basic jQuery coding tutorial. I’m happy to discuss in general what your code needs to do, but I’m not going to write the code for you. I don’t want you to think you could be getting more than I can provide, so managing expectations at this point seems prudent.

    Yes, you can add any number of functions to the same Ajax hooks. For additions using the same priority argument (defaults to 10 when not supplied), the order of execution is undefined. I don’t see how it would help in this case. A single page request for a particular category would result in all functions being called.

    The other thing you could do is add functions to multiple, different Ajax hooks. Each category could have its own action: value, resulting in a different action hook firing, which tells the function which category to query. You could do this, but it’s not necessary. Ideally, you want a single function to do everything, the only difference is which data values are sent as part of the request.

    Since initially, all lists start at page 1, there is no need to localize anything related to pagination. You still need the other localized data, but not the current page. It’s always going to be 1. Higher numbers for the initial page request do not make sense when you lazy load additional content via Ajax.

    There’s any number of ways to keep track of the current page of each category. I would simply initialize a global JS array on load in which the current pages are managed. The HTML output should be modified in a way that makes it easy for jQuery to determine which category to request. For example, each load more button could have an ID attribute that’s the category term ID or slug. All the buttons should also have a common class not used elsewhere.

    The common class makes it easy for jQuery to get all elements with that class and create a current page array keyed by the unique ID representing the category. All values would initially be 1.

    Keeping track of the current page correlates nicely with other WP pagination routines, but it makes more sense to me to keep track of the next offset to request. This removes one level of abstraction. Thus the initial value would be the posts per page value. If posts per page is 10, the offset for the next query will also be 10. (the initial page of 10 posts are indexed 0-9)

    When the document is ready, get all load more buttons and build a global array keyed by each button’s ID. The resulting array would be structured something like this:

    {
      my-category: 10,
      parent-cat:  10,
      child-cat:   10,
      foo:         10,
      bar:         10
    }

    When a load more button is clicked, the element clicked is available as this. Get the ID attribute of this and fetch the associated offset value from our global array. Include this value in place of the ‘page’ value in the Ajax request data array. Upon a successful Ajax reply, increment the associated global array element value by the posts per page value. The first additional page’s offset is 10, so increment it to 20 for the next request, then to 30, etc. (when posts per page is 10)

    The Ajax request data array would also include the button’s ID attribute, because that tells PHP what category to query for. The ‘offset’ query argument will be used instead of ‘paged’. This disables all the strange pagination things WP does when ‘paged’ is used. By using ‘offset’ and ‘posts_per_page’, you end up with a more “honest” query that is more predictable in its results.

    In summary, the goal is to have a single Ajax routine manage all lazy loading of every category on the page, letting the page’s HTML and our global array provide the data needed for a proper query in each case.

    Hello @bcworkz,
    Thank your for your explanation. I was able to run the pagination correctly for each of the terms independently. The methods you instructed me were quiet easy to implement.
    Thank You again.
    The last thing I’m stuck is on the post repeat. I want to show fixed number of posts on load more when load more is clicked. I’m able to control the number of posts to load at a time but what i’m getting is repeated posts on each load. That means the posts that are shown on page load gets loaded again on load more click. I tried using offset parameter but couldn’t get it done properly.
    This is what I have done:

    $display_count = $default_posts_per_page; //from reading settings number of posts
    // Next, get the current page
    $page = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1; //tried using page from ajax too
    
    // After that, calculate the offset
    $offset = ( $page - 1 ) * $display_count;
    $args = array(
                'post_type' => 'trip', // Your Post type Name that You Registered
                'order' => 'ASC',
                'tax_query' => array(
                    array(
                        'taxonomy' => $keys[0], //activities taxonomy
                        'field' => 'slug',
                        'terms' => $_POST['second_class'], //term slug from ajax
                        'include_children' => false
                    )
                ),
                'number'     =>  $display_count,
      	    'page'       =>  $page,
      	    'offset'     =>  $offset
            );

    Can you please help me on this last query?

    Hello,
    I did it myself 🙂 I was able to do it the other way around. Any ways, thanks you for the support. You really are a WordPress guru.

    CHEERS!

    Moderator bcworkz

    (@bcworkz)

    You’re most welcome! I’m happy that you were able to work it all out, nicely done!

    Thank you again @bcworkz,
    Closing this ticket and marking as resolved.

Viewing 9 replies - 1 through 9 (of 9 total)
  • The topic ‘Ajax Load More on Hierarchical Taxonomy Terms’ is closed to new replies.