1

I'm trying to do an AJAX request in an WordPress plugin. The request is initiated on the front page embedded in an article using a custom shortcode.

I tried to combine infos from the documentation and this tutorial.

However something is going wrong when testing that method... the AJAX query hook isn't called.

This is how my page.php script is called for short code delivery:

add_shortcode('testshortcode', 'TestShortCodeFunction');

function TestShortCodeFunction($attributes)
{
   ob_start();
   include(__DIR__.'/page.php');
   return ob_get_clean();
}

This is my compacted test script page.php:

<?php

    add_action('wp_ajax_test', 'Test');
    add_action('wp_ajax_nopriv_test', 'Test');

    // do_action('wp_ajax_nopriv_test'); this calls Test()

    function Test()
    {
        echo 'Hallo';
        die();
    }
?>

<script type='text/javascript'>

    jQuery(document).ready(function() {

        console.log("JS query started");

        // The following does not call test()
        jQuery.ajax({
            type: "POST",
            url: "http://127.0.0.1/wordpress/wp-admin/admin-ajax.php?action=test",
            success: function(data) {
                console.log("Query returned: "+data);
            }
        });

    });

</script>

Output on the console is:

JS query started
Query returned: 0 

The Test() function is never call according to the PHP debugger.

The admin-ajax.php is executed according to the network monitor (URL http://127.0.0.1/wordpress/wp-admin/admin-ajax.php?action=test) but returns 0.

Inside admin-ajax.php do_action('wp_ajax_test') is called according to the PHP debugger.

brasofilo
  • 25,496
  • 15
  • 91
  • 179
Silicomancer
  • 8,604
  • 10
  • 63
  • 130
  • Can you edit the question to include your js please? – Andrew Bartel Mar 22 '14 at 19:07
  • See my updated question. However I am not sure if this important since my PHP debugger catches the right admin-ajax.php call. This is why I suppose that the JS is ok. Do you know the internals of do_action()? What does it mean if it returns at the posted code line? – Silicomancer Mar 22 '14 at 19:35
  • Does it work if you just go to whatever comes of /wp-admin/admin-ajax.php?action=querydata? That's not always the right path. Look at how core does it, or the popular plugins and themes with wp_localize_script. The correct php function to get the path is admin_url( 'admin-ajax.php' ) btw. If this is in the admin section, you also just have ajaxurl available. console.log() it and see if it's the same output. – Andrew Bartel Mar 22 '14 at 20:19
  • The result is http://127.0.0.1/wordpress/wp-admin/admin-ajax.php?action=querydata. If I call this directly the same happens as when relaoding the plugin page... admin-ajax.php is called, do_action() is called with 'querydata' but the connected callback isn't called. This drives me nuts. All my hooks worked before :-( – Silicomancer Mar 22 '14 at 20:33
  • Before what? I'd start from the basics. Add a function that just echos hello or something. Use the ajax hooks to hook into that and run a jquery ajax that logs the result, and then build from there, adding on anything that needs to run in the function and building out with the extjs stuff. Here's a functional example in an answer of mine: http://wordpress.stackexchange.com/questions/96795/using-jquery-to-delete-data-stored-in-wp-options/96801#96801 – Andrew Bartel Mar 22 '14 at 20:38
  • Ok. I did so. I created a very basic code using a plain jQuery AJAX request. It behaves like the code before. See me totally revised question. Any idea? – Silicomancer Mar 22 '14 at 21:52
  • Try wrapping the ajax action inside an init action function. Also don't use both actions, if the ajax is intended to be activated from not logged in users, use nopriv only. – m1r0 Mar 22 '14 at 22:12
  • 1
    Put the js in a separate file, use jQuery(document).ready(function() {}); instead of the ext stuff and include it correctly with wp_enqueue_script, should work then. I did that on a base wordpress install and it worked fine. I'm not familiar with extjs so I'm not sure how it actually handles document readiness. – Andrew Bartel Mar 22 '14 at 22:13
  • I tried to do so. Seems there is a problem how I execute the above code because the wp_enqueue_scripts hook also does not work in that place. Please note that I am executing the above script inside a short code hook function. I added the details to my question. – Silicomancer Mar 22 '14 at 22:55
  • I'm not sure it matters, but with POST the parameters go in the request body, not the url (as they would in a GET request). – pguardiario Mar 22 '14 at 23:10

1 Answers1

5

I'd be really surprised if you managed to make those Ajax action hooks work inside an Output Buffer.
AFAIK, we should only use PHP ob_* functions as last resort.

Here's a standard implementation. A JavaScript file will be enqueued inside the shortcode callback function, and inside it we fire a document.ready Ajax call. The admin-ajax.php URL is passed to a JS object using wp_localize_script. Check the code comments for more info and the Codex for details on each WP function:

<?php
/**
 * Plugin Name: (SO) Simple Ajax Shortcode
 * Description: Fire an Ajax action when rendering a shortcode
 * Author:      brasofilo
 * Plugin URL:  https://stackoverflow.com/a/22585520/1287812
 */

add_shortcode( 'testshortcode', 'shortcode_so_22579460' );
add_action( 'wp_enqueue_scripts', 'enqueue_so_22579460' );
add_action( 'wp_ajax_Test_SO', 'Test_SO' );
add_action( 'wp_ajax_nopriv_Test_SO', 'Test_SO' );

/**
 * Enqueue our script inside the shortcode 
 */
function shortcode_so_22579460($attributes)
{
    wp_enqueue_script( 'my-script' );
    return '<h1>Check the broswer console.</h1>';
}

/**
 * Register and localize our script
 */
function enqueue_so_22579460() 
{
    wp_register_script( 
         'my-script',
         plugin_dir_url( __FILE__ ) . 'ajax.js',
         array( 'jquery' ) // enqueue jQuery as dependency
    );

    // We can pass any number of PHP data to the JS 'wp_ajax' object
    // Here, only the basics, Ajax URL and a security check 
    wp_localize_script( 
        'my-script', 
        'wp_ajax',
        array( 
              'ajaxurl'     => admin_url( 'admin-ajax.php' ),
              'ajaxnonce'   => wp_create_nonce( 'ajax_post_validation' ) 
         ) 
    );
}

/**
 * Ajax callback
 */
function Test_SO()
{
    // Security check
    check_ajax_referer( 'ajax_post_validation', 'security' );

    // Demonstrative boolean value to return
    $random = ( rand() % 2 != 0 );

    if( $random )
        wp_send_json_error( array( 'error' => 'Random is ODD' ) );
    else
        wp_send_json_success( 'Random is EVEN' );
}

And the JavaScript file (ajax.js) stored in the same folder as the plugin file:

jQuery(document).ready( function($) 
{
    var data = {
        action: 'Test_SO',
        security: wp_ajax.ajaxnonce
    };

    $.post( 
        wp_ajax.ajaxurl, 
        data,                   
        function( response )
        {
            // Errors
            if( !response.success )
            {
                // No data came back, maybe a security error
                if( !response.data )
                    console.log( 'AJAX ERROR: no response' );
                // Error from PHP
                else
                    console.log( 'Response Error: ' + response.data.error );
            }
            // Success
            else
                console.log( 'Response Success: ' + response.data );
        }
    ); 
});

Here's a similar answer of mine using OOP and jQuery commands to trigger the action. And an article of interest.

Community
  • 1
  • 1
brasofilo
  • 25,496
  • 15
  • 91
  • 179
  • 1
    Great code example! Thanks! Also useful links! I implemented it the way above except I was using the OO approach adding the code to my short code class. First the good thing: All the JS stuff now works like a charm, including separate file, localize and enqueue.Now the bad thing: Test_SO() still isn't called. It's the same as before: Everything is called as expected (including the do_action('wp_ajax_Test_SO'); in admin-ajax.php) but not Test_SO(). Finally it says "AJAX ERROR: no response". I have no clue. Should I post my class in a separate answer? – Silicomancer Mar 23 '14 at 09:51
  • glad to know :) let me see a gist/pastebin – brasofilo Mar 23 '14 at 10:04
  • Wow. I think I just found the problem by accident. This is my test code: https://gist.github.com/anonymous/0b1ff173518ce8f51691. And this is how it is called: https://gist.github.com/anonymous/215bd7f630ab5362b58d. If I move the "new MyShortCodeHandler" line into the if "is_admin()" branch of the Application constructor it works. But why? I do not want to create the short code handler for the backend, it is useless there. It should be front end only in the final plugin. – Silicomancer Mar 23 '14 at 12:27
  • 2
    Before checking your code: Ajax runs on the admin side. If you encapsulate the callbacks inside `if(!is_admin()) {}`, Ajax won't work. – brasofilo Mar 23 '14 at 12:45
  • Ok, I see. So I need to instanciate the short code handler class all the time. For both, front- and back-end. However I would like to prevent the front-end only code from being executed at the backend. Obviously the AJAX stuff does not belong to that category. But I suppose I could wrap all short code related code into "is_admin()" conditions, right? – Silicomancer Mar 23 '14 at 12:51
  • 1
    I think `add_shortcode` is pretty harmless in the backend. And `wp_enqueue_scripts` only runs on the frontend. I would move `$this->shortCodeHandler = new MyShortCodeHandler;` out of any `if/else`. IMHO, you're trying to micro-manage this. – brasofilo Mar 23 '14 at 13:02
  • Ok. I will do so. The only remaining task now is to make the AJAX query work with ExtJS data store proxy instead of using a jQuery post since using a ExtJS data grid for display was my original goal. I will try this later... currently my head is about to explode. – Silicomancer Mar 23 '14 at 13:15
  • Never worked with ExtJS, but taking a brief look seems a matter of enqueueing all its necessary files and sending data to `Application.js` through `wp_localize_script`. – brasofilo Mar 23 '14 at 13:42
  • Ok. ExtJS works purty fine too :-) Using right JSON paths and an "extraParams" property for the proxy worked well with the WP functions. Finally one issue left. In your example you are using wp_localize_script() to pass variables from PHP to JS when registering the client-side JS (before the actual short code is executed). What can I do if I want to forward a short code parameter to JS? May I call wp_localize_script() again, directly before wp_enqueue_script()? – Silicomancer Mar 23 '14 at 16:34
  • 2
    Great! I started learning web programming two weeks ago. I never used JS, PHP, JSON, AJAX, ExtJS, WP, jQuery, XAMPP or XDebug before. With the above I would have been lost without you. Thank you for your patience! – Silicomancer Mar 23 '14 at 17:13