1

PHP/HTML:

<ul id="load-more-div"></ul>
<a id="load-more" data-ppp="<?php echo get_option('posts_per_page'); ?>">load more</a>

JavaScripts:

(function($) {
  // Grab the load more button, since I only want to run the code if the button is on the page
  var loadMoreButton = $("#load-more");

  if (loadMoreButton) {
    // Get the posts_per_page number set in Reading Options
    var ppp = loadMoreButton.data("ppp");

    // Initialize function
    var loadPosts = function(page) {
      var theData, loadMoreContainer, errorStatus, errorMessage;

      // The AJAX request
      $.ajax({
        url: "/wp-json/wp/v2/posts",
        dataType: "json",
        data: {
          // Match the query that was already run on the page
          per_page: ppp,
          page: page,
          type: "post",
          orderby: "date"
        },
        success: function(data) {
          // Remove the button if the response returns no items
          if (data.length < 1) {
            loadMoreButton.remove();
          }

          // Create a place to store exactly what I need
          // Alternatively, the response can be filtered to only return the needed data, which is probably more efficient as the following loop wont be needed
          theData = [];

          // Get only what I need, and store it
          for (i = 0; i < data.length; i++) {
            theData[i] = {};
            theData[i].id = data[i].id;
            theData[i].link = data[i].link;
            theData[i].title = data[i].title.rendered;
            theData[i].content = data[i].content.rendered;
          }

          // Grab the container where my data will be inserted
          loadMoreContainer = $("#load-more-div");

          // For each object in my newly formed array, build a new element to store that data, and insert it into the DOM
          $.each(theData, function(i) {
            loadMoreContainer.append(
              '<li><a href="' +
                theData[i].link +
                '">' +
                theData[i].title +
                "</a></li>"
            );
          });
        },
        error: function(jqXHR, textStatus, errorThrown) {
          errorStatus = jqXHR.status + " " + jqXHR.statusText + "\n";
          errorMessage = jqXHR.responseJSON.message;

          // Show me what the error was
          console.log(errorStatus + errorMessage);
        }
      });
    };

    // Since our AJAX query is the same as the original query on the page (page 1), start with page 2
    var getPage = 2;

    // Actually implement the functionality when the button is clicked
    loadMoreButton.on("click", function() {
      loadPosts(getPage);
      // Increment the page, so on the next click we get the next page of results
      getPage++;
    });
  }
})(jQuery);

This is the trouble part, it doesn't remove the link.

// Remove the button if the response returns no items
if (data.length < 1) {
  loadMoreButton.remove();
}

Console errors when click the load more link after reaching the end of posts:

400 Bad Request
The page number requested is larger than the number of pages available.

Stickers
  • 75,527
  • 23
  • 147
  • 186

2 Answers2

2

I found two ways to solve it:

###Using data attribute

Get the max number of pages in the template, assign it to a data attribute, and access it in the scripts. Then check current page against total page numbers, and set disabled states to the load more button when it reaches the last page.

PHP/HTML:

<ul id="ajax-content"></ul>

<button type="button" id="ajax-button" data-endpoint="<?php echo get_rest_url(null, 'wp/v2/posts'); ?>" data-ppp="<?php echo get_option('posts_per_page'); ?>" data-pages="<?php echo $wp_query->max_num_pages; ?>">Show more</button>

JavaScripts:

(function($) {
    var loadMoreButton = $('#ajax-button');
    var loadMoreContainer = $('#ajax-content');
    if (loadMoreButton) {
        var endpoint = loadMoreButton.data('endpoint');
        var ppp = loadMoreButton.data('ppp');
        var pages = loadMoreButton.data('pages');
        var loadPosts = function(page) {
            var theData, errorStatus, errorMessage;
            $.ajax({
                url: endpoint,
                dataType: 'json',
                data: {
                    per_page: ppp,
                    page: page,
                    type: 'post',
                    orderby: 'date'
                },
                beforeSend: function() {
                    loadMoreButton.attr('disabled', true);
                },
                success: function(data) {
                    theData = [];
                    for (i = 0; i < data.length; i++) {
                        theData[i] = {};
                        theData[i].id = data[i].id;
                        theData[i].link = data[i].link;
                        theData[i].title = data[i].title.rendered;
                        theData[i].content = data[i].content.rendered;
                    }
                    $.each(theData, function(i) {
                        loadMoreContainer.append('<li><a href="' + theData[i].link + '">' + theData[i].title + '</a></li>');
                    });
                    loadMoreButton.attr('disabled', false);
                    if (getPage == pages) {
                        loadMoreButton.attr('disabled', true);
                    }
                    getPage++;
                },
                error: function(jqXHR) {
                    errorStatus = jqXHR.status + ' ' + jqXHR.statusText + '\n';
                    errorMessage = jqXHR.responseJSON.message;
                    console.log(errorStatus + errorMessage);
                }
            });
        };
        var getPage = 2;
        loadMoreButton.on('click', function() {
            loadPosts(getPage);
        });
    }
})(jQuery);

###Using jQuery complete event

Get the total pages x-wp-totalpages from the HTTP response headers. Then change the button states when reaches last page.

PHP/HTML:

<ul id="ajax-content"></ul>

<button type="button" id="ajax-button" data-endpoint="<?php echo get_rest_url(null, 'wp/v2/posts'); ?>" data-ppp="<?php echo get_option('posts_per_page'); ?>">Show more</button>

JavaScripts:

(function($) {
    var loadMoreButton = $('#ajax-button');
    var loadMoreContainer = $('#ajax-content');
    if (loadMoreButton) {
        var endpoint = loadMoreButton.data('endpoint');
        var ppp = loadMoreButton.data('ppp');
        var pager = 0;
        var loadPosts = function(page) {
            var theData, errorStatus, errorMessage;
            $.ajax({
                url: endpoint,
                dataType: 'json',
                data: {
                    per_page: ppp,
                    page: page,
                    type: 'post',
                    orderby: 'date'
                },
                beforeSend: function() {
                    loadMoreButton.attr('disabled', true);
                },
                success: function(data) {
                    theData = [];
                    for (i = 0; i < data.length; i++) {
                        theData[i] = {};
                        theData[i].id = data[i].id;
                        theData[i].link = data[i].link;
                        theData[i].title = data[i].title.rendered;
                        theData[i].content = data[i].content.rendered;
                    }
                    $.each(theData, function(i) {
                        loadMoreContainer.append('<li><a href="' + theData[i].link + '">' + theData[i].title + '</a></li>');
                    });
                    loadMoreButton.attr('disabled', false);
                },
                error: function(jqXHR) {
                    errorStatus = jqXHR.status + ' ' + jqXHR.statusText + '\n';
                    errorMessage = jqXHR.responseJSON.message;
                    console.log(errorStatus + errorMessage);
                },
                complete: function(jqXHR) {
                    if (pager == 0) {
                        pager = jqXHR.getResponseHeader('x-wp-totalpages');
                    }
                    pager--;
                    if (pager == 1) {
                        loadMoreButton.attr('disabled', true);
                    }
                }
            });
        };
        var getPage = 2;
        loadMoreButton.on('click', function() {
            loadPosts(getPage);
            getPage++;
        });
    }
})(jQuery);
Stickers
  • 75,527
  • 23
  • 147
  • 186
  • Thanks for this, it was super helpful! Using jQuery complete event definitely the way to go for me. – Trevor May 30 '19 at 06:42
1

The problem appears to be an invalid query to that endpoint so the success: function() is never being run in this circumstance.


Add to All API Errors

You could add the same functionality for all errors like this...

  error: function(jqXHR, textStatus, errorThrown) {
      loadMoreButton.remove();
      ....
  }

Though that may not be the desired way of handling of all errors.


Test for Existing Error Message

Another option could be to remove the button if you receive an error with that exact message...

  error: function(jqXHR, textStatus, errorThrown) {
      if (jqXHR.statusText === 'The page number requested is larger than the number of pages available.') {
         loadMoreButton.remove();
      }
      ....
  }

but this would be susceptible to breaking with any changes to that error message.


Return Custom Error Code from API

The recommended way to handle it would be to return specific error code (along with HTTP status code 400) to specify the exact situation in a more reliable format...

  error: function(jqXHR, textStatus, errorThrown) {
      if (jqXHR.statusCode === '215') {
         loadMoreButton.remove();
      }
      ....
  }

Here's an example on how to configure error handling in an API: Best Practices for API Error Handling


Return 200 HTTP Status Code

The last option would be to change the way your API endpoint handles this type of "error"/situation, by returning a 200 level HTTP status code instead, which would invoke the success: instead of the error: callback instead.

cantuket
  • 1,582
  • 10
  • 19
  • Thanks, I wish there is a way to do it without triggering the error. – Stickers Feb 24 '19 at 22:12
  • Ya in this case I think it's really personal preference on whether `no more results` would be considered an `error` (400 code) from the standpoint of the API. If you are building the API as well then you could just change it to return a `200` HTTP status code in this situation. In most cases I think an API designer would generally use a 400 code though. – cantuket Feb 24 '19 at 22:20
  • Just because there's an error from a query or API doesn't mean there's an error with your program. You just need to handle them properly. – cantuket Feb 24 '19 at 22:27
  • After some searching we might be able to compare the current page in the request URL with [X-WP-TotalPages](https://i.stack.imgur.com/czZic.png) in the header, but I could find a way to get the numbers in the scripts. Any idea if you're interested? – Stickers Feb 25 '19 at 01:05
  • It looks like that is an option, but you will have to handle that in the same `error:` callback unless you provide custom configuration for your Ajax request to accept 400 status codes as `success` – cantuket Feb 25 '19 at 02:06
  • Here’s how to access your headers in jQuery Ajax requests: [jQuery and Ajax Response Headers](https://stackoverflow.com/a/4236041/3329866) – cantuket Feb 25 '19 at 02:12