This entry is part 3 of 3 in the WordPress Rewrite API Series
- WordPress Rewrite API – Part 1
- WordPress Rewrite API – Part 2
- WordPress Rewrite API – Part 3
In part 3 of the WordPress Rewrite API series, we will be covering adding custom endpoints to specific places of your blog content. Endpoints are extremely powerful, and in this case, they will allow us to enable AJAX in the default theme WordPress (Twenty Eleven), so that when loading a post or page, it’s all done without reloading the page, via AJAX.
How does it work?
There are multitudes of ways to AJAXify WordPress themes. Chris Coyier has made a very detailed screencast about one way of doing it. The main idea is that the JavaScript script loads the HTML page and then inserts it inside the DOM replacing the old page content.
This has the advantage of being simple, easy to implement and works out of the box for most of the themes. But there is still a huge inconvenience of doing it that way. AJAX was introduced to avoid page reloads and make loading pages faster; loading the whole page HTML when you only need the post content and comments is an unnecessary overhead (and bandwidth) that could be avoided.
Another disadvantage is that all pages will be rendered with AJAX. It works in most cases, but in some cases you do NOT want to have the page loaded with ajax, since you might need to reload necessary JavaScript and CSS files, or render the page with a completely different HTML structure.
The proposed solution in this tutorial will approach both issues mentioned above. For the first, we’ll create a JSON output for each AJAX enabled page. For the second, we’ll give the JavaScript a way to check that the page is AJAX enabled, if not, it’ll load normally as it used to.
Obviously, our solution is not perfect. Since we are loading the JSON data only with JavaScript, we need to know the HTML structure of the post content and comments in advance to be able to construct it. This means that our solution is theme specific (in this tutorial, we assume that you have activated WordPress default theme “Twenty Eleven”) which is good in my opinion. Cross-theme solutions will certainly come at the expense of performance.
Adding an endpoint
The add_rewrite_endpoint function allows us to add custom end points like /trackback/ and /format/xml. This could be done by the rewrite API functions detailed in the previous tutorials of the series but this function is a lot easier: You don’t have to add a rewrite rule, a rewrite tag; and you get to define “places” where the endpoint will be added. Finally, you hook the function call with the init action. There is no need to refresh the rewrite rule on plugin activation and deactivation.
For example, if you want to add an endpoint to the comment pages, you simply do it with one line of code
<?php add_rewrite_endpoint('endpoint', EP_COMMENTS) ?> |
If you want to add the endpoint to all pages in your blog
1 2 3 | <?php add_rewrite_endpoint('endpoint', EP_ALL) ?> |
The endpoint mask is flexible. For example, you can add the endpoint to both the search and tag pages
1 2 3 | <?php add_rewrite_endpoint('endpoint', EP_SEARCH + EP_TAGS) ?> |
A query variable with the same name as the endpoint will be created. Before the template gets rendered (‘template_redirect’ filter) this variable can be read with the WordPress get_query_var function.
Generating the JSON output
We’ll be creating a JSON output for all pages in our blog. In addition to that, we’ll be creating a verification page which will serve as a check that the page is JSON enabled. The purpose of this page is to make the most out of performance. The verification page will return a short string of text “enabled” if the page supports AJAX calls.
To generate the JSON output, PHP has the json_encode function. The function accepts any type of data (except a resource) and converts it to a JSON string. Generally, it’s either an array or an object. We are using both here: An array with two keys “post” and “comments” which are objects.
<?php // Adding the json endpoint add_action( 'init', 'ao_add_json_endpoint' ); function ao_add_json_endpoint() { add_rewrite_endpoint('json', EP_ALL ); } // Template redirect add_action( 'template_redirect', 'ao_template_redirect' ); function ao_template_redirect() { global $post; switch (get_query_var('json')) { case 'verify': echo 'enabled'; exit; case 'response': $arr = array ('post' => $post, 'comments' => get_comments(array('post_id' => $post->ID))); echo json_encode($arr); exit; } } ?> |
The ao_json_endpoint registers ‘/json/’ as a valid endpoint to all pages; and also ‘json’ as a query variable. The ao_template_redirect function evaluates the query variable before the template is rendered and renders the right content.
It’s worth mentioning that limiting AJAX enabled pages should be done in the ‘verify’ switch case where we are returning ‘enabled’ for all pages in our case. Limiting from the add rewrite endpoint function is possible, but will return a full page (404 not found for pages outside the scope of the endpoint mask) which is against the idea of minimizing the size of the request.
So to check that the page is AJAX enabled, you call in your browser the following (which should return ‘enabled’ if it is)
http://blogurl.com/blog_post/json/verify |
To load the JSON version of a blog post, you need to call in your browser URL
http://blogurl.com/blog_post/json/response |
This is a little inconvenient and unusual if you ask me. There is nothing wrong with it, but this format is a friendlier, shorter and feels more natural
http://blogurl.com/blog_post/json |
If you load this URL, though, the ‘json’ endpoint will be ignored and the normal blog post page will be loaded. This makes sense since the query variable is not set. The query variable needs to be set somehow.
WordPress has a “request” filter that applies to the query variables that are passed to the default main SQL query when the page is called. We can hook to this function, and check if the ‘json’ query variable is set and null. In this case, we set the ‘json’ query variable to ‘response’ our default target.
1 2 3 4 5 6 7 8 9 | <?php add_filter('request', 'ao_set_queryvar'); function ao_set_queryvar($vars) { if (isset($vars['json']) && $vars['json'] === '') { $vars['json'] = 'response'; } return $vars; } ?> |
On the front-end
On the front-end, we’ll need to hijack every internal URL click event in the page and attach it to a function that performs a few things
- Loads the verification page and evaluates the returned content
- If the page is AJAX Enabled, load the JSON data and insert it into the page
- If the page is not AJAX Enabled, redirect the browser to the clicked URL
To detect internal URLs, I’m using a jQuery plugin called urlinternal coded by Ben Alman. It’s usage is pretty simple
1 | $('a:urlInternal') |
This returns all internal URLs in the page. It’s possible to detect external URLs, and fragment links. Refer to the plugin page for a complete description and documentation.
Parsing JSON is easy with the jQuery parseJSON function. It turns a JavaScript JSON string to a JavaScript object. Using “console.info(returned_json)”, you can get a better idea about hierarchy of the returned JSON object.
Note, I have added all of the necessary code to create the HTML structure of the page that matches the Twenty Eleven theme. It’s pretty straight forward, so it should not need explaining, but if you have questions, ask in the comments.
The JavaScript Code
Create a js folder in your plugin directory and save the URL internal jQuery plugin and this snippet in it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | (function($) { $(document).ready(function() { function create_article(post_title, post_content) { var title = '<header class="entry-header"><h1 class="entry-title">' + post_title + '</h1></header>', content = '<div class="entry-content">' + post_content + '</div>'; var html = '<article class="page type-page status-publish hentry">' + title + content + '</article>'; return html; } function display_page (data) { var post_title = data.post.post_title, post_content = data.post.post_content, post_guid = data.post.guid; $('#content').html(create_article (post_title, post_content)); } $('a:urlInternal').on('click', function(e) { e.preventDefault(); $('#content').html('loading...'); var url = $(this).attr('href'); $.get(url + 'json/verify/', function(data) { if (data === 'enabled') { $.get(url + 'json/', function(data) { display_page($.parseJSON(data)); }); } else { window.location = url; } }); }); }); })(jQuery); |
The PHP code
Save this PHP code into your main plugin file and activate it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <?php /* Plugin Name: WordPress AJAX Plugin URI: http://wpajax.com Description: WordPress AJAX Author: Abid Omar Version: 1.0 */ // Enqueue Scripts add_action('wp_enqueue_scripts', 'ao_enqueue_scripts'); function ao_enqueue_scripts() { $plugin_path = trailingslashit(WP_PLUGIN_URL . '/' . plugin_basename(dirname(__FILE__))); wp_enqueue_script ( 'ao_urlinternal', $plugin_path . 'js/jquery.ba-urlinternal.min.js', array('jquery') ); wp_enqueue_script ( 'ao_ajax', $plugin_path . 'js/ajax.js', array('jquery')); } // Adding the json endpoint add_action( 'init', 'ao_add_json_endpoint' ); function ao_add_json_endpoint() { add_rewrite_endpoint( 'json', EP_ALL ); } // Activating the json query variable add_filter('request', 'ao_set_queryvar'); function ao_set_queryvar($vars) { if (isset($vars['json']) && $vars['json'] === '') { $vars['json'] = 'response'; } return $vars; } // Template redirect add_action( 'template_redirect', 'ao_template_redirect' ); function ao_template_redirect() { global $post; switch (get_query_var('json')) { case 'verify': echo 'enabled'; exit; case 'response': $arr = array ('post' => $post, 'comments' => get_comments(array('post_id' => $post->ID))); echo json_encode($arr); exit; } } |
You can now test the plugin by loading your site’s home page (running the Twenty Eleven theme) and clicking on any post / page link. The page will be loaded with AJAX.
Moving further
This tutorial can be the beginning of an AJAX enabled theme solution. There are a lot of shortcomings that can’t be covered in a single tutorial, but this should be a really good starting point.
- Browser History; and enabling the next and previous button
- Loading comments which will require looping through the comments array
- Enabling AJAX for comments and forms
- A custom output for the home page and other special pages
If you are a premium subscriber, then the complete plugin is free to download below.
Join the Site to Download
For anyone else that might be having issues, I had to go to the Permalinks page in the admin to get this to work. Until I did that, I would get a 404 error when clicking on a link.
Also, and I may be wrong about this, but you have the template_redirect being an add_filter, which I think should be add_action. Not sure if it makes much of a difference though. And the first code part, the first function you have it being an add_filter and then in the full code at the end you have it as an add_action. I think both should be add_action.
But great tutorial. Would have come in really handy a few weeks ago when I was trying to do something similar.
Ah, yes, sometimes permalinks will need to be refreshed, but not always.
Technically it will work with add_action( ‘template_redirect’, ‘ao_template_redirect’ ); or add_filter( ‘template_redirect’, ‘ao_template_redirect’ );, but I believe add_action is better. I’ve updated the code.
Thanks for the input!
Permalinks need to be refreshed every time you change the rewrite rules (on plugin activation/deactivation). Sometimes another plugin refresh the rules and break yours. For a plugin I made, I found that the solution is to check that your rewrite rules are in the wp_rewrite object otherwise to add them again and refresh another time.
And you are correct on the “template_redirect” hook. It’s listed as an action in WordPress codex, so better use add_action for that.
Hi Pippin,
This is totally irrelevant to the topic..but this is more of general doubt.
I am using the update-notifier.php downloaded from codecanyon site which is used to notify the customers about the plugin updates and it looks like you are also using the same file as I could see that one of your customers reported that they faced a problem
“Fatal error: Call to undefined function get_plugin_data() in /(MY PATH)/wp-content/plugins/easy-content-types/update-notifier.php on line 57”
from the page…http://codecanyon.net/item/easy-custom-content-types-for-wordpress/discussion/234182?page=21
In reply to this you said that this issue has been fixed..can you please suggest me how you have suggested this issue in your plugin as it would be of tremendous help to me as well
Thanks,
Anuroop
To fix the issue, you just need to wrap your include(‘custom-plugin-updater.php’) call inside of if(is_admin()) { … }. The get_plugin_data() function only exists inside of the admin area.
This is awesome…it works…thanq so much man 🙂
Hi,
Would it be possible to create a permalink structure containing terms from multiple taxonomies like that:
domain.com/%custom_post_type%/taxonomy1_term/%taxonomy2_term%/%postname%/
I can’t handle that so I wonder if it’s possible at all.
Regards,
Bart
Hi Pippin, I can’t download the plugin. Could you please fix the download link?
The download link is still broken.
Hi Pippin, I can’t download the plugin. Could you please fix the download link?
Hi Pippin,
Please fix the download link.
Also, does this tutorial need an update?
Thanks in advance.