JavaScript

Javascript is the future of WordPress, but there are a number of things to keep in mind.

While there’s a lot of things that should always be done, there are three approaches to using WordPress javascript the right way.

  • The Wrong Way - Sending AJAX requests to files in your theme or page templates, then including them in your header with a manually coded tag
  • The Old Way - Using the WP AJAX API for requests
  • The Best Way - Building your admin UI in Javascript instead of PHP, and powering it with the REST API.

At the time of writing, the REST API and the content endpoints are the future, and admin UIs need to prepare for a fully JS powered admin UI. This means:

  • Clean, portable, cachable Data APIs
  • Enforced, and simplified built in security in your endpoints
  • A standardised system to work in
  • More secure interfaces by avoiding the need for escaping with Javascript templating and reactive libraries
  • Faster admin screens whose sole job is to bootstrap the JS UI

While information on these are compiled, information is preserved below.

Registering and Enqueueing

WordPress comes with dependency management and enqueueing for JavaScript files. Don’t use raw <script> tags to embed JavaScript.

JavaScript files should be registered. Registering makes the dependency manager aware of the script. To embed a script onto a page, it must be enqueued.

Let’s register and enqueue a script.

  1. // Use the wp_enqueue_scripts function for registering and enqueueing scripts on the front end.
  2. add_action( 'wp_enqueue_scripts', 'register_and_enqueue_a_script' );
  3. function register_and_enqueue_a_script() {
  4. // Register a script with a handle of `my-script`
  5. // + that lives inside the theme folder,
  6. // + which has a dependency on jQuery,
  7. // + where the UNIX timestamp of the last file change gets used as version number
  8. // to prevent hardcore caching in browsers - helps with updates and during dev
  9. // + which gets loaded in the footer
  10. wp_register_script(
  11. 'my-script',
  12. get_template_directory_uri().'/js/functions.js',
  13. array( 'jquery' ),
  14. filemtime( get_template_directory().'/js/functions.js' ),
  15. true
  16. );
  17. // Enqueue the script.
  18. wp_enqueue_script( 'my-script' );
  19. }

Scripts should only be enqueued when necessary; wrap conditionals around wp_enqueue_script() calls appropriately.

When enqueueing javascript in the admin interface, use the admin_enqueue_scripts hook.

When adding scripts to the login screen, use the login_enqueue_scripts hook.

Localizing

Localizing a script allows you to pass variables from PHP into JS. This is typically used for internationalization of strings (hence localization), but there are plenty of other uses for this technique.

From a technical side, localizing a script means that there will be a new <script> tag added right before your registered script, that contains a global JavaScript object with the name you specified during localizing (the 2nd argument). This also means that if you add another script later on, that has this script as dependency, then you will be able to use the global object there as well. WordPress resolves chained dependencies just fine.

Let’s localize a script.

  1. add_action( 'wp_enqueue_scripts', 'register_localize_and_enqueue_a_script' );
  2. function register_localize_and_enqueue_a_script() {
  3. wp_register_script(
  4. 'my-script',
  5. get_template_directory_uri().'/js/functions.js',
  6. array( 'jquery' ),
  7. filemtime( get_template_directory().'/js/functions.js' ),
  8. true
  9. );
  10. wp_localize_script(
  11. 'my-script',
  12. 'scriptData',
  13. // This is the data, which gets sent in localized data to the script.
  14. array(
  15. 'alertText' => 'Are you sure you want to do this?',
  16. )
  17. );
  18. wp_enqueue_script( 'my-script' );
  19. }

In the javascript file, the data is available in the object name specified while localizing.

  1. ( function( $, plugin ) {
  2. alert( plugin.alertText );
  3. } )( jQuery, scriptData || {} );

Deregister / Dequeueing

Scripts can be deregistered and dequeued via wp_deregister_script() and wp_dequeue_script().

AJAX

WordPress offers an easy server-side endpoint for AJAX calls, located in wp-admin/admin-ajax.php.

Let’s set up a server-side AJAX handler.

  1. // Triggered for users that are logged in.
  2. add_action( 'wp_ajax_create_new_post', 'wp_ajax_create_new_post_handler' );
  3. // Triggered for users that are not logged in.
  4. add_action( 'wp_ajax_nopriv_create_new_post', 'wp_ajax_create_new_post_handler' );
  5. function wp_ajax_create_new_post_handler() {
  6. // This is unfiltered, not validated and non-sanitized data.
  7. // Prepare everything and trust no input
  8. $data = $_POST['data'];
  9. // Do things here.
  10. // For example: Insert or update a post
  11. $post_id = wp_insert_post( array(
  12. 'post_title' => $data['title'],
  13. ) );
  14. // If everything worked out, pass in any data required for your JS callback.
  15. // In this example, wp_insert_post() returned the ID of the newly created post
  16. // This adds an `exit`/`die` by itself, so no need to call it.
  17. if ( ! is_wp_error( $post_id ) ) {
  18. wp_send_json_success( array(
  19. 'post_id' => $post_id,
  20. ) );
  21. }
  22. // If something went wrong, the last part will be bypassed and this part can execute:
  23. wp_send_json_error( array(
  24. 'post_id' => $post_id,
  25. ) );
  26. }
  27. add_action( 'wp_enqueue_scripts', 'register_localize_and_enqueue_a_script' );
  28. function register_localize_and_enqueue_a_script() {
  29. wp_register_script(
  30. 'my-script',
  31. get_template_directory_uri().'/js/functions.js',
  32. array( 'jquery' ),
  33. filemtime( get_template_directory().'/js/functions.js' ),
  34. true
  35. );
  36. // Send in localized data to the script.
  37. wp_localize_script(
  38. 'my-script',
  39. 'scriptData',
  40. array(
  41. 'ajax_url' => admin_url( 'admin-ajax.php' ),
  42. )
  43. );
  44. wp_enqueue_script( 'my-script' );
  45. }

And the accompanying JavaScript:

  1. ( function( $, plugin ) {
  2. $( document ).ready( function() {
  3. $.post(
  4. // Localized variable, see example below.
  5. plugin.ajax_url,
  6. {
  7. // The action name specified here triggers
  8. // the corresponding wp_ajax_* and wp_ajax_nopriv_* hooks server-side.
  9. action : 'create_new_post',
  10. // Wrap up any data required server-side in an object.
  11. data : {
  12. title : 'Hello World'
  13. }
  14. },
  15. function( response ) {
  16. // wp_send_json_success() sets the success property to true.
  17. if ( response.success ) {
  18. // Any data that passed to wp_send_json_success() is available in the data property
  19. alert( 'A post was created with an ID of ' + response.data.post_id );
  20. // wp_send_json_error() sets the success property to false.
  21. } else {
  22. alert( 'There was a problem creating a new post.' );
  23. }
  24. }
  25. );
  26. } );
  27. } )( jQuery, scriptData || {} );

ajax_url represents the admin AJAX endpoint, which is automatically defined in admin interface page loads, but not on the front-end.

Let’s localize our script to include the admin URL:

  1. add_action( 'wp_enqueue_scripts', 'register_localize_and_enqueue_a_script' );
  2. function register_localize_and_enqueue_a_script() {
  3. wp_register_script( 'my-script', get_template_directory_uri() . '/js/functions.js', array( 'jquery' ) );
  4. // Send in localized data to the script.
  5. $data_for_script = array( 'ajax_url' => admin_url( 'admin-ajax.php' ) );
  6. wp_localize_script( 'my-script', 'scriptData', $data_for_script );
  7. wp_enqueue_script( 'my-script' );
  8. }

The JavaScript side of WP AJAX

There are several ways to go on this. The most common is to use $.ajax(). Of course, there are shortcuts available like $.post() and $.getJSON().

Here’s the default example.

  1. /*globals jQuery, $, scriptData */
  2. ( function( $, plugin ) {
  3. "use strict";
  4. // Alternate solution: jQuery.ajax()
  5. // One can use $.post(), $.getJSON() as well
  6. // I prefer defered loading & promises as shown above
  7. $.ajax( {
  8. url : plugin.ajaxurl,
  9. data : {
  10. action : plugin.action,
  11. _ajax_nonce : plugin._ajax_nonce,
  12. // WordPress JS-global
  13. // Only set in admin
  14. postType : typenow,
  15. },
  16. beforeSend : function( d ) {
  17. console.log( 'Before send', d );
  18. }
  19. } )
  20. .done( function( response, textStatus, jqXHR ) {
  21. console.log( 'AJAX done', textStatus, jqXHR, jqXHR.getAllResponseHeaders() );
  22. } )
  23. .fail( function( jqXHR, textStatus, errorThrown ) {
  24. console.log( 'AJAX failed', jqXHR.getAllResponseHeaders(), textStatus, errorThrown );
  25. } )
  26. .then( function( jqXHR, textStatus, errorThrown ) {
  27. console.log( 'AJAX after finished', jqXHR, textStatus, errorThrown );
  28. } );
  29. } )( jQuery, scriptData || {} );

Note that above example uses _ajax_nonce to verify the NONCE value, which you will have to set by yourself when localizing the script. Just add '_ajax_nonce' => wp_create_nonce( "some_value" ), to your data array. You can then add a referrer check to your PHP callback that looks like check_ajax_referer( "some_value" ).

AJAX on click

Actually it’s pretty simple to execute an AJAX request when some clicks (or does some other user interaction) on some element. Just wrap up your $.ajax() (or similar) call. You can even add a delay like you might be used to.

  1. $( '#' + plugin.element_name ).on( 'keyup', function( event ) {
  2. $.ajax( { ... etc ... } )
  3. .done( function( ... ) { etc }
  4. .fail( function( ... ) { etc }
  5. } )
  6. .delay( 500 );

Multiple callbacks for a single AJAX request

You might come into a situation where multiple things have to happen after an AJAX request finished. Gladly jQuery returns an object, where you can attach all of your callbacks.

  1. /*globals jQuery, $, scriptData */
  2. ( function( $, plugin ) {
  3. "use strict";
  4. // Alternate solution: jQuery.ajax()
  5. // One can use $.post(), $.getJSON() as well
  6. // I prefer defered loading & promises as shown above
  7. var request = $.ajax( {
  8. url : plugin.ajaxurl,
  9. data : {
  10. action : plugin.action,
  11. _ajax_nonce : plugin._ajax_nonce,
  12. // WordPress JS-global
  13. // Only set in admin
  14. postType : typenow,
  15. },
  16. beforeSend : function( d ) {
  17. console.log( 'Before send', d );
  18. }
  19. } );
  20. request.done( function( response, textStatus, jqXHR ) {
  21. console.log( 'AJAX callback #1 executed' );
  22. } );
  23. request.done( function( response, textStatus, jqXHR ) {
  24. console.log( 'AJAX callback #2 executed' );
  25. } );
  26. request.done( function( response, textStatus, jqXHR ) {
  27. console.log( 'AJAX callback #3 executed' );
  28. } )
  29. } )( jQuery, scriptData || {} );

Chaining callbacks

A common scenario (regarding how often it is needed and how easy it then is to hit the mine trap), is chaining callbacks when an AJAX request finished.

About the problem first:

AJAX callback (A) executes
AJAX Callback (B) doesn’t know that it has to wait for (A)
You can’t see the problem in your local install as (A) is finished too fast.

The interesting question is how to wait until A is finished to then start B and its processing.

The answer is “deferred” loading and “promises”, also known as “futures”.

Here’s an example:

  1. ( function( $, plugin ) {
  2. "use strict";
  3. $.when(
  4. $.ajax( {
  5. url : pluginURl,
  6. data : { /* ... */ }
  7. } )
  8. .done( function( data ) {
  9. // 2nd call finished
  10. } )
  11. .fail( function( reason ) {
  12. console.info( reason );
  13. } );
  14. )
  15. // Again, you could leverage .done() as well. See jQuery docs.
  16. .then(
  17. // Success
  18. function( response ) {
  19. // Has been successful
  20. // In case of more then one request, both have to be successful
  21. },
  22. // Fail
  23. function( resons ) {
  24. // Has thrown an error
  25. // in case of multiple errors, it throws the first one
  26. },
  27. );
  28. //.then( /* and so on */ );
  29. } )( jQuery, scriptData || {} );

Source: WordPress.StackExchange / Kaiser