0

I’m trying to create my own Load More functionality (on click) in my child theme using the latest standards in Wordpress. I’m not comfortable working with the php parts of things and I’m getting very confused with what is actually needed. Now I am aware there are lots and lots of similar questions in here but it is really hard to pick bits and pieces of information from random posts, all with similar but not equal problems/answers. With that said I'm sorry if I upset someone by asking.
Spoiler alert - English is not my native language, so please be thorough and... patient with me.

The content I want to fetch through AJAX is a custom post type. I don’t know if it’s at all important but I’m placing my custom post type content above the_content because the design demands this solution.

It’s my understanding that a Load More function requires the usage of REST, am I right? Otherwise there’s no way to combine the power of php and javascript.
To do this I’m using the wp_localize_script() method. I started out using ’ajaxurl’ => admin_url(’admin-ajax.php’). However, I have since then been informed that this is the old way of doing things and I don’t want no oldies. Instead I’m trying to use rest_url().

All in all, this is what I have produced this far (or mostly copied and pasted to be honest 😄):

Functions.php:

function itc_included_files() {
    wp_register_script('itc-load-more-js', get_stylesheet_directory_uri() . '/build/assets/js/load-more.js', array('jquery'), false, true);

    // Give our js script access to the REST
    wp_localize_script( 'itc-load-more-js', 'load_ajax_data', array(
        'restURL' => rest_url(),
        'restNonce' => wp_create_nonce('wp_rest')
        )
    );

    wp_enqueue_script('itc-load-more-js');
}

Included in the absolute top of functions.php like so:

require_once(get_stylesheet_directory() . '/inc/itc-load-more-ajax.php');

is this code snippet down below:

<?php
function itc_load_more() {
  
    $ppp = (isset($_POST["ppp"])) ? $_POST["ppp"] : 1;
    $page = (isset($_POST['pageNumber'])) ? $_POST['pageNumber'] : 0;
  
    header("Content-Type: text/html");
  
    $args = array(
        'suppress_filters' => true,
        'post_type' => 'radgivare',
        'posts_per_page' => $ppp,
        'paged' => $page,
    );
  
    $loop = new WP_Query($args);
  
    $out = '';
  
    if ($loop->have_posts()) : while ($loop->have_posts()) : $loop->the_post();
            $out .= get_template_part('template-parts/content', 'page');
        endwhile;
    endif;
    wp_reset_postdata();
    die($out);
}
  
add_action('wp_ajax_nopriv_itc_load_more', 'itc_load_more');
add_action('wp_ajax_itc_load_more', 'itc_load_more');
?>

Template file

<section class="advisor__wrapper">
    <h2 class="title-lg is-outlined">Large heading</h2>

    <?php
    $agent_posts = new WP_Query([
        'post_type' => 'radgivare',
        'post_status' => 'publish',
        'posts_per_page' => 4,
        'orderby' => 'post_date',
        'order' => 'DESC'
    ]);

    if ($agent_posts->have_posts()) : ?>
        <div id="load-more-posts-container" class="itc-row gutter">
            <?php
            while ($agent_posts->have_posts()) : $agent_posts->the_post();
                get_template_part('template-parts/content', 'radgivare');
            endwhile;
            ?>
        </div>
    <?php endif; ?>
    <?php wp_reset_postdata(); ?>
    <input type="hidden" id="totalpages" value="<?= $loop->max_num_pages ?>">
    <div class="advisor__wrapper__btn-wrapper">
        <a href="" class="btn rounded btn-margin-lg btn--violet" id="btn-load-more">Visa fler rådgivare</a>
    </div>
</section>

And finally my js file:

var ppp = 1; // Post per page
var pageNumber = 2;
var total = jQuery('#totalpages').val();

jQuery("#btn-load-more").on("click", function ($) { // When btn is pressed.
    jQuery("#btn-load-more").attr("disabled", true); // Disable the button, temp.
    pageNumber++;
    var str = '&pageNumber=' + pageNumber + '&ppp=' + ppp + '&action=itc_load_more';
    jQuery.ajax({
        type: "GET",
        dataType: "json",
        url: load_ajax_data.restURL,
        data: str,
        success: function (data) {
            var $data = jQuery(data);
            if ($data.length) {
                jQuery("#load-more-posts-container").append($data);
                jQuery("#btn-load-more").attr("disabled", false);
            } else {
                jQuery("#btn-load-more").attr("disabled", true);
            }                
            if (total < pageNumber) {
                jQuery("#btn-load-more").hide();
            }
        },
        error: function (jqXHR, textStatus, errorThrown) {
            $loader.html(jqXHR + " :: " + textStatus + " :: " + errorThrown);
        }

    });
    return false;
});

Originally the type was set to "POST" and the dataType to "html". Neither works.

My code is clearly missing some fundamental part because it simply will not work. Educative and constructive help on this matter is greatly appreciated because I don’t know what I’m doing wrong. Thank you!

8
  • The REST API Handbook is the primary entry-point to REST API documentation. If your post type is already accessible on the REST API, you should already be able to retrieve a list of 'radgivare' posts from the path /wp-json/wp/v2/radgivare on your site (assuming default configurations).
    – bosco
    Commented Aug 16, 2022 at 3:03
  • Thank you @bosco for replying! Yes it is accessible from the REST, but what does that mean speaking of the javascript (jquery)? Where would I put that uri? Are you saying I can skip the whole AJAX request in favour of for instance a WP_Query and still make things work? I do want the page to load new posts without a page refresh though.
    – Seblito
    Commented Aug 16, 2022 at 6:28
  • What does this mean: I think it would be valid to include that rendered template markup as a property within the response - but the response itself should remain as JSON. Can you please clarify what I have to do?
    – Seblito
    Commented Aug 16, 2022 at 6:39
  • Are you saying I can skip the whole AJAX request in favour of for instance a WP_Query and still make things work? Yes - if all you need is the data from the post. That is, your JavaScript can take the raw post_content and transform it into DOM elements. What does this mean ...? I mean that if your JavaScript cannot, or if you do not want your JS to assemble the DOM - like if you're really relying on that PHP get_template_part() call to determine the markup/DOM for additional posts - then we need to modify the REST API response to include that rendered HTML.
    – bosco
    Commented Aug 16, 2022 at 8:14
  • Either solution is totally feasible and possible - it's just a matter of how you'd like to accomplish your end-goal here - do you want to construct the markup for the new posts in JavaScript, or do you want them to run through that get_template_part() - that is, to construct the markup with PHP from a template file? Ultimately, I think you're probably seeking the latter, if this is a traditional theme - it's easier to define the output for a post in PHP than it is in JS, unless you're wholly committed to using a front-end framework like React, which I don't believe is the case here.
    – bosco
    Commented Aug 16, 2022 at 8:16

1 Answer 1

0

Background

As an overly broad comparison of the legacy AJAX handler system and the REST API:

  • The AJAX handler system is little more than a way to connect a request to specific PHP file to a WordPress hook callback, which leaves virtually all other implementation details up to the developer.
  • The REST API is more like a "HTTP API framework" which provides a variety of systems, tools, and conventions to aide in exposing data and functionality at logical URIs in an organized fashion.

The REST API also comes complete with all of the necessary functionality to expose most WordPress data and all general interactions with it automatically. Out of the box, you can Create/Read/Update/Delete WordPress users, pages, posts, taxonomies, etc. through standardized HTTP requests to conventional endpoints.

As you've already seen, the process of exposing the core data for a Custom Post Type on the REST API can be as simple as flipping a switch in the CPT's register_post_type() call:

register_post_type(
  'radgivare',
  [
    // ...
    'show_in_rest' => true
  ]
);

After which the post type is exposed through the REST API at the route

/wp-json/wp/v2/radgivare

And specific posts of that type through the parameterized route

/wp-json/wp/v2/radgivare/<id>

No need to write all the logic to query for, create, update, or delete your CPT through an HTTP request like you would need to if you wanted to perform these operations using the legacy AJAX handler system - the REST API has taken care of the basics for you, which is pretty neat :)

The type of interaction with the data exposed at a route is determined by the HTTP Method of the request. A POST request is used to update or insert. A DELETE request will delete. A GET request will retrieve/query for data - which also means that visiting a route directly in your browser will execute this sort of operation (I recommend using a JSON-prettifying browser extension when exploring REST API routes in a browser so you don't loose your mind).

This all just scratches the surface of the REST API - I highly recommend spending some time reading the REST API Handbook to become more acquainted with it's concepts and systems. It's also a handy reference for the core endpoints, and which parameters they accept and the format of the data they return.

The core REST API routes are also self-documenting - this means that detailed descriptions of each of the core routes are available as data in a response. Primarily through the GET /wp-json/wp/v2 route. The Handbook is a lot easier to look at, but sometimes you can find a reference for fields and functionality which has been added by plugins in the response.


Solution

There are many ways in which could deliver a post's content as rendered through a template part. One solution would be to add a field to the the REST API's response for a GET request for your CPT which will contain the template-rendered markup.

Because get_template_part() simply executes the referenced PHP file, the markup in that file will be sent to output (e.g. echo'd) instead of returned - it's not possible to acquire the markup as a string via something like $out = get_template_part(...);. Instead, we need to use PHP "output buffers" to capture the output, then dump it to a string.

function wpse408606_rest_radgivare_templated_content_field() {
  // Add a new field to responses to requests for `radgivare` posts, which
  // will be populated with the return value from the `get_callback`.
  register_rest_field(
    'radgivare',
    'templated_content',
    [
      'get_callback' => 'wpse408606_get_radgivare_templated_content',
    ]
  );
}

add_action( 'rest_api_init', 'wpse408606_rest_radgivare_templated_content_field' );

function wpse408606_get_radgivare_templated_content() {
  ob_start(); // Create a new, empty buffer to capture any proceeding output.

  // The $post object is already set up as though we're in The Loop.
  // Load the template.
  get_template_part( 'template-parts/content', 'page' );

  return ob_get_clean(); // Close the output buffer, and dump it's contents as a string.
}

That's all that's necessary on the PHP side. A new field named templated_content will now be available in the responses to requests for radgivare posts, containing the output of that template part.

We don't need add any code to handle which page or how many posts to return, because the default REST controller for posts already facilitates these parameters.

Your jQuery code becomes like

jQuery("#btn-load-more").on("click", function ($) { // When btn is pressed.
    jQuery("#btn-load-more").attr("disabled", true); // Disable the button, temp.
    pageNumber++;

    var rest_route = `${load_ajax_data.restURL}wp/v2/radgivare?page=${pageNumber}&per_page=${ppp}`;

    jQuery.ajax({
        type: "GET",
        dataType: "json",
        url: rest_route,
        data: str,
        success: function (data) {
            for( var i = 0; i < data.length; i++ ) {
                $post_content = jQuery( data[i].templated_content );
                jQuery("#load-more-posts-container").append($ post_content );
            };
           
            if (total < pageNumber) {
                jQuery("#btn-load-more").hide();
            }
            else {
                jQuery("#btn-load-more").attr("disabled", false);
            }
        },
        error: function (jqXHR, textStatus, errorThrown) {
            $loader.html(jqXHR + " :: " + textStatus + " :: " + errorThrown);
        }

    });
    return false;
});

There are many ways in which this could be improved, and it likely still needs to be adapted for your specific use-case, but I hope it's enough to get you started!

1
  • Thank you so much! I really appreciate you taking the time! I will try this out first thing as soon as I get back in front of my laptop 😊
    – Seblito
    Commented Aug 20, 2022 at 19:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.