This is an unusually long and dense article! It is necessary to adequately cover this topic.
You will often see
Nextto Contents
links. Of course, the "to Contents" link will bring you back here. The "Next"
link just takes you to the section immediately below. Not that useful, except that it makes
it easy to bookmark where you left off so you can return straight to it at a later time.
AJAX is a common technique used in modern web applications, so many developers are familiar with it's use.
Unfortunately, how it is implemented in WordPress is unique, so seasoned web developers new to WordPress
will need to alter their ways slightly. If you are new to AJAX, getting it working in your WordPress plugin
by referring to the
Codex
examples will likely be more confusing than helpful. I hope to remove this
potential confusion here and properly illustrate the "WordPress Way" of using AJAX to developers
of all skill levels. You experienced types can skim ahead now while I get our
novices caught up.
AJAX is the abbreviation for Asynchronous JavaScript And XML. In contrast to
the traditional HTML model
where entire pages of data are sent for each request, AJAX makes a request in response to various events
on a page, then only updates the effected elements of the page based on the response. Not only does this
reduce the amount of data that needs to be sent, but the user experience becomes much more interactive.
WordPress uses AJAX for many Administration Panel functions, but the default theme (currently twentytwelve)
uses none at all for the public side of WordPress. However, many themes do utilize AJAX for various
public user experiences.
In order to successfully enable an AJAX interchange, you must create both client side jQuery (or
JavaScript) code and server side PHP code. While JavaScript alone without the jQuery libraries can
be used for AJAX, this article will focus on jQuery primarily because the selectors provide much
more flexibility in choosing HTML DOM elements and the libraries are already loaded by default
for WordPress. AJAX without jQuery can be problematic due to inconsistent browser implementations (looking at
you, IE!) of straight JavaScript AJAX.
An AJAX exchange always begins with some sort of event on the user's page in their browser, typically
a mouse click, but any event could serve as a trigger. The event initiates an associated jQuery
function which sends a request to your server, including any pertinent information the server needs
to process the request.
When the server receives such a request, it does what it's supposed to do based on the information
received. What this is is entirely up to you and only limited by the PHP language, your specific server
limitations, and the data available. Though not required, the server would typically send a response
(often formatted as XML) once it's programmed tasks are completed.
The jQuery function that sent the initial request will receive the response and do something with it,
usually displaying something to the user as confirmation that the initial event either succeeded or
failed to produce the desired sequence of events. That's AJAX in a nutshell, now on to actually making it work.
1.1 Files
The code for AJAX can be contained within one of a few WordPress features: Plugin, Theme, or Child
Theme. The examples used in this article are for implementation as a Plugin, though they can be easily
adapted for theme use. PHP files are typically stored in the plugin's or theme's main folder. For
themes, the code would almost always be in the functions.php file. The plugin file can be named anything,
but the main plugin PHP file must have specific data in the initial comments identifying it as the
main plugin file. The code could also be in an auxillary file that is referenced in the main plugin file with an
include or require statement of some sort.
The JavaScript or jQuery code can be echoed into the page's header via PHP script, but I
prefer to compartmentalize JavaScript code in separate .js files contained in a /js/ sub-folder
under the main plugin or theme folder. This arrangement makes maintenance of the code easier and
follows the arrangement used by many programmers. The following examples reflect this arrangement.
Nextto Contents
2. jQuery (Client side)
This section covers the jQuery code required to both send a request and handle the response. The
server side PHP code for receiving the request and handling a response is covered in the
PHP section.
2.1 Wrapper
While much of this section covers client side AJAX in general, one aspect specific to WordPress
and a few other applications is the dollar sign ($) jQuery shortcut is not directly available for you
to use in your code. This because the jQuery library is loaded by WordPress in the
"no conflict" mode.
You can still use the $ shortcut in your code if your code is contained within a "no conflict" wrapper:
jQuery(document).ready(function($) {
// $() will work as an alias for jQuery() inside of this code block "wrapper"
});
2.2 Selector
Causing your jQuery code to react to an event begins by using jQuery selector arguments to specify
which DOM object should be monitored for an event. These are the CSS like descriptors contained
in the parenthesis immediately following the initial $ or jQuery code. Selectors are well
documented
elsewhere, so we will not get into any detail here. Here is a basic example of selecting a table cell
with a class value of "author" (<td class="author">):
$(td.author);
Note that each line of code in JavaScript and jQuery, as well as PHP and mySQL, all terminate
with a semi-colon (;).
2.3 Event
There are many events that your code can watch for to initiate an AJAX exchange. One commonly
used event is a mouse click. Another common one is a form element change in value. There are
also keyboard and window events available to trigger events. Events are also well
documented
elsewhere, so we will not cover them in detail. This is an example of having the function toggle_onoff()
execute when a click event occurs on a table cell of the class "author":
$(td.author).click(toggle_onoff);
Since event handler functions are rarely reused elsewhere, instead of naming a function to pass
to the event method, it's very common to define an anonymous function right inside the method's
parenthesis. This is sometimes referred to as a closure, because variables in closures have a
special scope which you would not see in named functions. This can give closures a big advantage
over named functions, especially ones related to AJAX. An anonymous event handler would look like:
$(td.author).click(function() {
// insert code here to execute when table cell is clicked on
});
Notice JavaScript and PHP both share the same type of C style comment delimiters:
// and /* */
When a DOM element's event triggers a handler function,
you can use the variable this to refer to the element. This is very useful because
there may be several <td> elements of class "author", but only one of
them was clicked on, the this one. There may not be any other selector that works
for this one object. For example, even if there are many table cells of the class "author",
the following code will change the color of only the cell that was clicked on to red:
jQuery uses two different ways to send a request to the server, GET and POST. We will focus on the
POST method because it affords a little more privacy and is more conducive for submitting
form data. While event handlers can execute any valid JavaScript code, AJAX events will
all at least contain a .get(), .post() or related method. Very often, one or the
other is the only code that needs to be executed. Adding the .post() method into
our event handler results in something like this:
Let's now take a look at the different parameters passed in the .post() method. There is
actually a fourth optional parameter that specifies the data type returned from the server.
The default of letting jQuery make an intelligent guess typically works fine for nearly every
situation, so you will rarely see the fourth parameter.
2.4a URL
The server_url parameter for the .post method is the PHP page which will handle the request.
In WordPress, this is always the same page. Conveniently, WordPress defines a
local AJAX class object to be used as a receptacle for AJAX exchanges. The class object is instantiated in WordPress's PHP
localize script
function, we will name ours ajax_object. It keeps the url in a property
we will name ajax_url. Assigning these names will be covered in a related
PHP section later.
You don't want to hard code an URL here because it would only
work for one site, you couldn't offer your plugin for anyone else to use without the need
to edit every URL in every AJAX call. You instead construct the proper URL for each site with
PHP and save it in the localized object. We will also cover this later. For now,
you just need to know that you access the url in JavaScript as:
ajax_object.ajax_url
For admin side AJAX, the server_url is also available as the global variable ajaxurl.
Since this is not defined for public side AJAX, for the sake of consistency, I suggest you always
pass the URL in the localized AJAX object and not use the global version.
Nextto Contents
2.4b Data
The submit_data parameter will vary for each application, though you will always see one or two
common elements. This is the data you pass to the server, which it will act on in some way. The
data passed is often the contents of a form. All the data is assembled into an associative array
to be passed to .post() as a single element.
2.4b(1) Nonce
Any AJAX request that could result in a change to the database needs to be secured by a nonce, a
portmanteau of "Number used ONCE". It is essentially a unique hexadecimal serial number
assigned to each instance of any form served. When the form's data is submitted, the nonce is
checked for validity by the server. Only forms with a valid nonce are accepted for processing.
For details on defining a nonce, see the
PHP Nonce
section. For now, all you need to know is a nonce value the server creates is added to the
ajax_object object as a named property. You will know the name because you will define
it in the PHP part of the AJAX implementation. Let's say it's name will be author.
The default array key for nonces is _ajax_nonce. You can use a different key, but it must
be coordinated with the PHP portion or else the nonce check will not know how to access the
passed value. The beginning of our array definition looks like this:
{_ajax_nonce: ajax_object.author}
2.4b(2) Action
The required action parameter is the name appended to the AJAX action hook in PHP to channel the
request to the correct code. If our action in our example is change, our data array becomes:
Again, this parameter is coordinated with the PHP code so everything works together.
2.4b(3) Data
Additional data is optional. Sometimes just the fact a request came in with a particular action
is enough information. Other times, form data needs to be sent. It all depends on the application.
If we want to send an author name in our request, our data array becomes:
If you wanted to get fancier, you could get the contents of the element clicked on by using the
this variable, but that's for another jQuery lesson.
Nextto Contents
2.4c Callback
The callback handler is the function to execute when a response comes back from the server after
the request is made. Once again, we usually see an anonymous function here. The function is passed
one parameter, the server response. The response could be anything from a yes or no to a huge XML
database. JSON formatted data is also a useful format for data. For our example, the response will
either be "yes" or "no". If the response is "yes", we simply change the
CSS color of the DOM element. The anonymous function looks like this:
function(data) {
if (data.search('yes')) {
$(this2).css('color', 'red');
}
}
Edit 8/18: For later PHP versions, using the echo statement in Ajax handlers means a simple
JS equivalency test like if ('yes' == data) is not adequate because additional lines are sent with just
echo 'yes';. We must search within the sent string for our value.
Notice the use of this2 to specify the <td> element clicked on, it is a copy
of the initiating event's this, that special variable mentioned earlier that differentiates
the one element clicked on
from all other elements matching the same selector that could have been clicked on and handled here.
The callback is out of
scope to use the original this, so we make a copy of it within scope of the action function for callback
use by inserting the following line before the call to .post() :
var this2 = this;
You can see how this fits into the complete code listing in the
jQuery Summary.
2.4c(1) Nonces
Note that when our code executes, we will have used the initial nonce contained in the ajax_object.author property
by sending it back to the server.
It normally cannot be used again. In order to make another request before a page reload, the server
would typically need to send a new nonce to use in the next request. Our callback function would need
to take this new nonce and place it in the property ajax_object.author.
As far as WordPress is concerned, it's "nonce" is not number used once, it is number used as
much as you want in 24 hours. It is not a true nonce implementation. So there is not really a need to
refresh the nonce on each request in WordPress. I mention how to refresh the value simply to illustrate
the proper use of a true nonce.
2.4c(2) XML
While XML is the X in AJAX, and it is the classic way to structure data, it's rather unwieldy and
modifying the structure by script can be difficult. Many programmers prefer JSON for it's light
weight and ease of use.
How an XML string is parsed depends on the browser, so for full compatibility, you need to implement
both parsers. Use the Microsoft.XMLDOM ActiveX object's
.loadXML()
method for Internet Explorer and the
DOMParser
object's .parseFromString() method for all other browsers.
2.4c(3) JSON
This format is based on JavaScript, so you can actually convert JSON text to an object simply with the
eval() function. However, using eval() carries some significant security risks, so
you should use a JSON parser, which will be faster as well. The global instance of the parser is
JSON, and it's parse method is
.parse(),
so do something like so:
obj = JSON.parse(txt);
To ensure the JSON parser is available to the browser, it must be enqueued on the originating PHP page.
An example is provided in the related
PHP Section.
2.4c(4) Other
As long as the data format is coordinated, it can be any format you like, such as comma delimited,
tab delimited, or any kind of structure that works for you.
Nextto Contents
2.5 Summary
Putting together all the various elements covered in this section, our .js file has the following contents:
jQuery(document).ready(function($) { //the wrapper
$(td.author).click(function() { //the selector and event
var this2 = this; //copy 'this' so that the callback can use it
$.post(ajax_object.ajax_url, { //the server_url
action: "change", //the submit_data array
_ajax_nonce: ajax_object.author,
author_name: "Elvis"
}, function(data) { //the callback_handler
if (data.search('yes')) {
$(this2).css('color', 'red');
}
});
});
});
This section covers the PHP code needed to handle an AJAX request and respond to it. The client
side jQuery code is covered in the
jQuery section.
Most of this PHP code is specific to WordPress and has no application in other environments.
3.1 Enqueue
In order for WordPress to recognize and load our .js file we created in the jQuery section, we need
to enqueue it. This is done by hooking either the 'admin_enqueue_scripts' action
for administration pages or the 'wp_enqueue_scripts' action for other pages.
Login pages require the use of the 'login_enqueue_scripts' action. The action
call passes the name of the page being loaded to your hook function, so you can control which page you load your script
on so that it is not unnecessarily loaded on every page. An example hook for the
edit-comments.php admin page looks like this:
function my_enqueue($hook) {
if( 'edit-comments.php' != $hook) return;
// add add enqueue stuff here
}
add_action('admin_enqueue_scripts', 'my_enqueue');
3.1a wp_enqueue_script()
You call the
wp_enqueue_script()
function to enqueue your JavaScript file. It takes up to 5 parameters, but you typically only need to
provide the first 3. The first is an arbitrary tag to identify your script, which can be almost anything.
Let's call ours 'ajax_script'. The second parameter is a complete path to our JavaScript file. For
flexibility, you never hard code path specifications, you use tags such as
plugins_url().
The third parameter is an array of dependent script tags. These are other scripts your script needs in order
for it to function properly. Any tags listed will have their associated
files included or loaded when your script loads. Our script is dependent only on jQuery, but we must still pass
it in an array. A sample enqueue script line looks like this:
As mentioned above under
jQuery Data,
a nonce serves to ensure data is submitted from a valid form.
You construct one here so you can pass it to the jQuery function so it can prove it's identity. You
use the function
wp_create_nonce().
It takes one parameter, an arbitrary string which is also used to check the nonce when it is submitted.
You assign the returned value to a variable for use in the localize function described next. Example:
To localize a script, you create an object that will be local to the JavaScript or jQuery script you enqueued previously.
You can attach any needed properties to this object using PHP and they will be available to your
JavaScript functions. This is one of the few ways PHP can pass parameters to JavaScript. You localize by calling
wp_localize_script().
The function takes 3 parameters. The first is the tag you used to enqueue the script. The second is
an arbitrary name for your AJAX object. The third parameter is an associative array of properties you
want available in your object. For the example in the next section, we are only passing a nonce and the URL for the PHP
WordPress AJAX handler page.
3.1c(1) URL
All WordPress AJAX requests get handled by the same file:wp-admin/admin-ajax.php.
Even for front end public functions that are not admin related, the request still goes to this page.
This has caused issues for people that have hardened access to the /wp-admin/ folder for security purposes.
Hardened access is fine, but if your theme uses front end AJAX, the admin-ajax.php file must
be excluded from the hardened access. The file needs to be accessible to all users. Also, once again, you never hard code URLs, you use the
appropriate tags, in this case
admin_url().
Our localize function will look like this:
wp_localize_script( 'ajax-script', 'ajax_object', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'author' => $author_nonce, //it is common practice to comma after
)); //the last array item for easier maintenance
All the names used here must be coordinated with the jQuery script for everything to properly come
together. In jQuery, the data assigned here is accessed as object properties. So the data we assigned
in wp_localize_script() will be referenced as ajax_object.ajax_url and
ajax_object.author in JavaScript.
Nextto Contents
3.2 AJAX Action
As noted above, all AJAX requests are routed to the same page. That page extracts the action parameter
contained in the request and invokes a custom action who's tag name is formed by the action
parameter's value being prefaced by either "wp_ajax_" or "wp_ajax_nopriv_".
Which preface is used depends on whether the current user is logged in or not. For front end
requests where the user may or may not be logged in, you should hook both actions. Since our
request has an action value of "change", and assuming the user must be logged in to do
this action, the hook used to handle our request will look like:
function my_change() {
// handle the ajax request
die; // all ajax handlers should die when finished
}
add_action('wp_ajax_change', 'my_change');
Which will result in the function my_change() being called when our request is received.
Attentive readers might wonder if they could use an anonymous function or closure to define the
action handler. You can, but closures are a recent PHP feature that is not supported in older
versions. For maximum backwards compatibility, you should avoid using closures for now.
Hooks are a significant part of WordPress development. If you are unfamiliar with this concept,
take a look at the
Codex
to get caught up on this important topic.
3.2a Referrer
For security purposes, if a nonce is used to secure the data exchange, one of the first things you
should do is verify the nonce. You use
check_ajax_referer().
It takes three parameters. The first is the exact same string we used for
wp_create_nonce().
The second is the key name under which the request's nonce is stored. If the default _ajax_nonce
was used as a key, no name need be specified. The default behavior when the nonce fails to verify is
to simply die. If you want the function to return true/false so additional code can be executed, pass
true as the third parameter. In our example, we can simply check the nonce with:
check_ajax_referer('author_example');
Note. The correct English spelling is 'referrer' with 4 'r's.
When the HTTP header specification
was written, no one noticed the the misspelled 'referer' with 3 'r's. So now,
all code related to the 'referer'
header is typically intentionally misspelled to match the specification. The Referer Header is supposed
to contain the page URL that sent the HTTP request, but this header is easily spoofed, so the wp_check_referer()
wants to see that the nonce value is correct before deciding the request came from a
legitimate page.
3.2b Capability
Unless the action to be performed is something anyone can do, you must verify the current user has the
proper capability to do the action. The capability should be related to the action being
performed. Custom capabilities can be assigned if need be. In order to allow an action based on role,
it's easiest to just identify a capability only owned by the role desired and specify that in the function
current_user_can().
It can lead to unpredictable results if you pass a role to this function. Example:
current_user_can('manage_options');
3.2c Action
The action handler function does whatever needs doing. It may involve adding or deleting something
in the database, or simply looking something up to send back to the client. In our quite silly
example, the handler checks if there is a match between the sent value "author" and an
option in the WordPress options table called 'author' and responds accordingly. An example
follows in the Summary section.
Nextto Contents
3.2d Response
A response is sent to the client by echoing the data. It can take several forms depending on the need.
3.2d(1) XML
XML is a common format to send structured data with. WordPress provides the
WP_Ajax_Response
class to assist in assembling XML responses to AJAX requests.
3.2d(2) JSON
JSON is another format for sending structured data which has some advantages: lightweight and ease of
use. There is no native WordPress support for this, but a few plugins exist to fill the void. The PHP function.
json_encode()
can be used to generate data strings from any PHP data structure, including arrays
and objects, but not resources.
To ensure the JSON parser in loaded for your JavaScript running on the client browser, first enqueue
it on your PHP page or as part of the 'init' hook.
wp_enqueue_script( 'json2' );
Then specify 'json2' as one of the dependencies when you enqueue your script in PHP.
The response can be in any format as long as it is coordinated with the JavaScript interpreting
the response. Our example simply returns either yes or no.
Early versions of this code had the wrong $_POST key specified,
this has since been corrected.
The $_POST array is a PHP super global which contains all the data included in the AJAX POST
request. You may see other references that use $_REQUEST instead of $_POST. $_REQUEST
is a merge of $_POST, $_GET, and $_COOKIE. Convenient, but a little dangerous
due to possible name collisions, where $_COOKIE takes precedence. An attacker could create
a cookie in your site's name, perhaps named 'action' which would in effect override any action
sent by your form. This would mean the attacker could possibly run any arbitrary PHP code that is on your
server, with the credentials WordPress runs under instead of a low privileged user. Highly unlikely to be
sure, but insecure none the less. Avoid using $_REQUEST.
3.2d(4) Die
By definition, all AJAX handlers must terminate their response with a call to the die() function
when they have completed all processing.
Nextto Contents
4. Summary
Our complete PHP code looks like this:
<?php
/* Enqueue required scripts and prepare needed data */
function my_enqueue($hook) {
if( 'edit-comments.php' != $hook) return;
wp_enqueue_script( 'ajax-script',
plugins_url( '/js/myjquery.js', __FILE__ ),
array('jquery')
);
$author_nonce = wp_create_nonce( 'author_example' );
wp_localize_script( 'ajax-script', 'ajax_object', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'author' => $author_nonce,
));
}
add_action('admin_enqueue_scripts', 'my_enqueue');
/* Handle an AJAX request */
function my_change() {
check_ajax_referer('author_example'); // dies without valid nonce
if( current_user_can('manage_options')) {
if( $_POST['author_name'] == get_option( 'author')){
echo 'yes';
} else {
echo 'no';
}
}
die; // all ajax handlers should die when finished
}
add_action('wp_ajax_change', 'my_change');
//The final ?> in PHP files is commonly omitted and is actually considered good practice
And once again, our myjquery.js file contains:
jQuery(document).ready(function($) { //the wrapper
$(td.author).click(function() { //the selector and event
var this2 = this; //copy 'this' so that the callback can use it
$.post(ajax_object.ajax_url, { //the server_url
action: "change", //the submit_data array
_ajax_nonce: ajax_object.author,
author_name: "Elvis"
}, function(data) { //the callback_handler
if (data.search('yes')) {
$(this2).css('color', 'red');
}
});
});
});
If you're curious, the code block syntax highlighting on this page is accomplished with the javascript module
google-code-prettify
using a modified variant of the supplied "Desert" style by techto...@.