WordPress plugin: render only page content using ajax call

Your code is a bit complex, so I can’t post a full-working code, but a proof of concept, then adapt to your code is up to you.

So, Ajax in WordPress should be used via Ajax API, instead to send a request to same url, that of course print header, and other stuff, simply send a request via Ajax Api and output what you want.

I assume that you have a code like:

add_shortcode( 'festival' , array( new Events_Controller, 'festival' ) );

and festival() method of Events_Controller class it the one you posted. It seems that the only 2 params you get from shortcode are 'collection_edition_id' and 'name' all other params are generated (I guess) by Events_Controller::initialize_params() method (probably based on request).

So, if your Events_Controller::festival() method can take arguments from another source than shortcode than it can do its work in the same way. Look at this code:

public function festival( Array $params = array() ) {

  if ( ! $this->isAjax() ) ) {
    $collection = isset( $params['collection_edition_id'] )
      ? $params['collection_edition_id']
      : 0;
    $festival = isset( $params['name'] ) ? $params['name'] : '' ;
  } elseif ( empty( $params ) ) {
    $collection = filter_input(INPUT_POST, 'collid', FILTER_SANITIZE_NUMBER_INT);
    $festival = filter_input(INPUT_POST, 'festival_name', FILTER_SANITIZE_STRING);
  }

  if ( empty($collection) || ! is_numeric($collection) ) {
    // WordPress Ajax API need to die() on end or will append a 0 to the response
    if ( $this->isAjax() ) die();
    return;
  }

  $this->initialize_params();
  $this->initialize_api_client();
  $date = array_key_exists( 'date', $this->params )
    ? $this->params['date']
    : date( 'Y-m-d' );
  $category = $this->params['category'];
  $page = isset( $this->params['page'] ) ? $this->params['page'] : 1;
  $events_by_festival = $this->api_client->from_day(
    $date, $collection_edition, $category, $page 
  );

  if ( ! $this->isAjax() ) ) {
    wp_enqueue_script(
      'my_ajax_script',
      plugins_url( 'js/my_ajax.js', __FILE__ ),
      array('jquery'), NULL, TRUE
    );
    $data = array( 'ajax_url' => admin_url( 'admin-ajax.php' ) );
    wp_localize_script( 'my_ajax_script', 'MyScriptData', $data );
  }

  if( $this->isAjax() ) {
     View::render(
       "events/partials/_list.php", array( 'events' => $events_by_festival )
     );
     // WordPress Ajax API need to die() on end or will append a 0 to the response
     die();
  } else {
    return View::render(
      "events/festival.html.php",
      array(
        'date' => $date,
        'category' => $category,
        'festival' => $festival,
        'collection' => $collection, // <-- pass also the collection
        'page' => $page, // <-- pass the page
        'events_by_festival' => $events_by_festival
      )
    );
  }

}

As you can see I made the $params argument optional and I make possible send to the method collection id and festival name via a POST ajax request, instead of via shortcode atts, the result is that you can run that method outside of a shortcode.

I’ve also used wp_enqueue_script to add a javascript file in the footer, and in that file (that I assumed is ‘js/my_ajax.js’ relative to your plugin folder) you should put the code to trigger the ajax call.

Finally using wp_localize_script I send to that script a variable: MyScriptData that will contain in the ajax_url property the correct url of 'admin-ajax.php' (i.e. inside that script you can use MyScriptData.ajax_url to access the url).

The method now became very “large” and probably is better split it into parts, e.g. the javascript stuff can be put in another method, just like the render stuff.

Now you have to create the ajax api call to run the method:

add_action( 'wp_ajax_myplugin_festival', array( new Events_Controller, 'festival' ) );
add_action( 'wp_ajax_nopriv_myplugin_festival', array( new Events_Controller, 'festival' ) );

previous code means that when you send a request to admin-ajax.php where $_REQUEST['action'] is equal to 'myplugin_festival' (the part after ‘wp_ajax_’ and ‘wp_ajax_nopriv’) then Events_Controller::festival is called by WordPress.

So the only thing that you need to do is send a POST request to admin-ajax.php where ‘action’, ‘collid’ and ‘festival_name’ POST variables are set correctly.

However ‘action’ must me hardcoded to ‘myplugin_festival’, but ‘collid’ and ‘festival_name’ should be set accordingly to what the shortcode are showing: the easiest way to do the trick is set a data- argument inside the html markup printed by shortcode.

In OP you didn’t posted your template, so I don’t know how it is structured, however as proof of concept I assume there is a container div, where you can set data:

<div class="festival-container" id="festival-container-<?php echo $collection; ?>"
   data-collid="<?php echo $collection; ?>"
   data-festival="<?php echo $festival; ?>"
   data-page="<?php echo $page; ?>">

Off course you need to pass to your templates $festival,$collection and $page variables.

So, your js file (that in previous code I assumed is 'js/my_ajax.js' file in your plugin folder) write:

jQuery(document).ready( function($) {

  $( document ).on( 'click', '.festival-container .show-more-button a', function(e) {  
    e.preventDefault();
    var $container = $(this).closest('.festival-container');
    var collection = $container.data('collid');
    var festival = $container.data('festival');
    var paged = $container.data('page') + 1;
    $.ajax( {
      url: MyScriptData.ajaxurl,
      data: {
        action: 'myplugin_festival',
        page: paged,
        coll_id: collection,
        festival_name: festival
      },
      type: 'POST',
      dataType: 'html',
      error: errorHandler
    }).done( function( response ) {
      $container.find('.list').append( response );
    });
  });

});

Some notes

Please see your page source to be sure that the javascript url is inserted correctly, because if the file path js/my_ajax.js is relative to plugin root then you need to pass as second argument to plugins_url the path of a file in the plugin main directory, where I used __FILE__, but if your Events_Controller class is in a subdirectory the url used for js/my_ajax.js will be wrong.

Also, in your js you are using a simple $('.list').append( response ); but probably you should a more selective selector than $('.list') also because if the same shortcode is inserted more than once you need a way to recognize to which shortcode append the response.
I’ve used a reference to container, adapt my code to your needs.

On the countrary, you trigger the ajax call on '#show-more-button a' but probably you should use an html class instead of an html id because you can have more than one button. I’ve used a class using the container class to narrow possibilities of conflicts with other stuff in the page, again, adapt my code to your needs.