0

I'm writing a plugins that renders json data from an API request and I need to implement an infinite scroll in a content page.

The problem is when I render the content with an ajax call, it renders all the page instead.

This is what I have done so far:

function enable_pagination() {
    jQuery(function($) {
        $('#show-more-button a').click( function(e) {
            e.preventDefault();
            $.ajax( {
                url: document.URL,
                data: { 'page': 2 },
                type: 'POST',
                dataType: 'html',
                error: errorHandler
            }).done( function( response ) {
                $('.list').append( response );
            });
        });
    });
}

EDIT: ADDING EVENTS_CONTROLLER CODE

public function festival( Array $shortcode_params ) {
            $collection_edition = $shortcode_params['collection_edition_id'];
            $festival_name = $shortcode_params['name'];

            if ( is_numeric( $collection_edition ) && $collection_edition > 0 ) {
                $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 );

                // pagination
                if( $this->isAjax() )
                    return View::render( "events/partials/_list.php", array( 'events' => $events_by_festival ) );
                else
                    return View::render( "events/festival.html.php", array( 'date' => $date, 'category' => $category, 'festival' => $festival_name, 'events_by_festival' => $events_by_festival ) );
            }
            return false;
        }


private function isAjax() {
            return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
        }

View is a custom class with some helpers ex: for rendering.

/**
         * Render the template, returning it's content.
         * @param $data array Data made available to the view.
         * @param $template string view file name to render
         * @return string The rendered template.
         */
        public static function render( $template, $data ) {
            extract( $data );
            ob_start();
            include( "views/$template" );
            $content = ob_get_contents();
            ob_end_clean();
            return $content;
        }

document.URL refers to an events_controller action that, when ajax request, returns only events_list partial view.

As I said before, in this case, ajax response contains, other than content, also the header, sidebar, footer, etc....

There is a clean way to achieve this?

Thank you in advance

  • Are you trying to modidy the value of document.URL? In most browsers you can get the document.URL but you can not set it. So, you are sending the ajax request to the current document.URL, i.e., you are getting the current webpage in the ajax call. – cybmeta Mar 24 '14 at 14:20
  • I'm sending the same request with the next page. Events_controller checks if it's an ajax request, than returns a partial views with the new events. But instead a get in return also header, sidebar, footer,... – Pietro Mar 24 '14 at 14:56
  • Post Events_controller code – cybmeta Mar 24 '14 at 15:02
  • I'm using a shortcode. – Pietro Mar 28 '14 at 10:02
  • 1
    I'm not sure I have understood what youre doing. What I got is: 1 You have a page/post where you place a shortcode 2 this shortcode trigger a function that run a query and show the post getted by using a template 3 the shortcode also output a link, and when you click it you want to run again the same query paginated and append result to the output of the shortcode. Is this correct? – gmazzap Mar 28 '14 at 12:19
2
+50

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.

1

As you already know you can use wp_ajax_* and wp_ajax_nopriv_* hook to handle ajax request. Let see it with an example how it works.

First we need to pick a Unique action key for our ajax request. For now say my_action. It is good idea to prefixed the key with your plugin name, so it would not conflict with others. Now define two hook. wp_ajax_my_action and wp_ajax_nopriv_my_action

if ( is_admin() ) {
    add_action( 'wp_ajax_my_action', 'my_action_callback' );
    add_action( 'wp_ajax_nopriv_my_action', 'my_action_callback' );
}

Why two hook:

The wp_ajax_my_action hook only called for logged-in users only wheres, wp_ajax_nopriv_my_action hook triggers when user is not logged-in. So you can chose which one you needed. If you need to allow ajax request for regardless user logged-in or not, you have to register both.

For admin side there is a javascript global variable ajaxurl. But for front-facing pages, the ajaxurl javascript global does not get automatically defined for you, unless you have BuddyPress or another Ajax-reliant plugin installed. So instead of relying on a global javascript variable, we declare our global variable. Only difference with the default ajaxurl is we are going to add our unique action parameter(my_action) to end of it.

addAction('wp_print_scripts', 'init_ajax_url_variable');
function init_ajax_url_variable()
{
    $url = admin_url("admin-ajax.php?action=my_action");
    echo '<script type="text/javascript">';
    echo "var my_action_ajax_url = '{$url}'";
    echo '</script>';
}

Now define your ajax call

function enable_pagination() {
    jQuery(function($) {
        $('#show-more-button a').click( function(e) {
            e.preventDefault();
            $.ajax( {
                url: my_action_ajax_url,
                data: { 'page': 2 },
                type: 'POST',
                dataType: 'html',
                error: errorHandler
            }).done( function( response ) {
                $('.list').append( response );
            });
        });
    });
}


function my_action_callback() { //Your function to handle ajax request;

   // Here is all your code

    exit; //stop further processing
}

Hope now you have clear idea how this works

Another shortest solution for your problem would be adding a die/exit after your ajax processing. If you do not need to process the out put of the festival function you can do this:

$viewContent = View::render( "events/partials/_list.php", array( 'events' => $events_by_festival ) );
die($viewContent);

Enjoy!

For resource Click Here

0

Your current code requests the entire page which comes back as response.

.done( function( response ) {
    $('.list').append( response );
})

Instead of appending the response to your .list element, you should break apart your response, find the chunk you need, and then only append the chunk to .list.

You can do this with jQuery's .find(). The code below analyzes the response and searches for the .list element on that page. By using jQuery's .html(), we can extract the .list element's HTML code, and pass it to the .append() from your original code.

.done(function( response ) {
    $( '.list' ).append( $( response ).find( '.list' ).html() );
})

If you want an endless scroll, you should probably remember the paged index, and increment it after every successful AJAX call, so that the next call will attempt to retrieve the next page's .list element.

  • Thank you for your answer but I was hoping in a better solution. There is an hook wp_ajax_ and wp_ajax_nopriv_, but they seems to work only for backend. – Pietro Mar 28 '14 at 13:33
  • These hooks work for both frontend and backend, but are for executing PHP code with an AJAX call. From what I gathered from your original post (before the events_controller edit), you are working with jQuery and require a jQuery solution. The code I provided will do what you ask: append the .list item with only the list from page 2 instead of the whole page. If this is not what you are really asking, please improve your question. – Marc Dingena Mar 28 '14 at 13:37
  • Yes, it does what I asked, but I'm looking for a better solution. An ajax request that returns only the content of the partial view. (I added events_controller to better explain.) – Pietro Mar 28 '14 at 13:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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