You indeed need to use AJAX to achieve this. Wordpress have its own way to handle ajax request that you can use for this by going through few steps detailed below. I will try to show some WP best practices on this subject - so as there could be faster/easier/dirtier way to achieve this (probably even through a plugin), this will make a good introduction for Wordpress theme/plugin development for you. Feel free to follow the docs links to understand better how this works.
1. Building your categories menu
In your code sample the menu is static, though you should better use a dynamic menu. One solution could be to use get_the_category_list
, but it doesn't allow you to have full control on category links. I would suggest to define a new navigation menu like this (the following code go into your theme functions.php):
add_action('after_setup_theme', 'add_categories_menu');
function add_categories_menu() {
register_nav_menu('categories_menu', __('Categories Menu', 'your-theme-slug'));
}
Then in your template, replace your static menu with the following in order to display your brand new menu:
<?php wp_nav_menu(array('theme_location' => 'categories_menu')); ?>
Now to add the categories to your menu, log into your Wordpress administration, go into
Appearance > Menus, create a new menu, select the theme location "Categories Menu" for it, and add the categories to it.
Last step for this, we'll add a filter in order to add onclick
attribute on the menu links that will send the category slug to a js function called showPostsFromCategory()
that we'll define later.
This go in your functions.php:
function add_onclick_attr_categories_menu($atts, $item, $args) {
if($args->theme_location == 'categories_menu' && $item->object == 'category') {
$category = get_category($item->object_id);
if($category !== null) {
$atts['onClick'] = 'showPostsFromCategory("' . $item->slug . '")';
}
}
return $atts;
}
add_filter('nav_menu_link_attributes', 'add_onclick_attr_categories_menu', 10, 3);
You may want to ask, why we're keeping the category link on the menu item? It's for SEO and accessibility purpose: for a browser without javascript (like a screen reader) or a search crawler, the categories pages will still be accessible.
2. Preparing your theme for AJAX
In Wordpress, all the AJAX requests need to be sent to wp-admin/admin-ajax.php
with an action parameter that will identify the request in order to catch it in functions.php using the wp_ajax_nopriv_my_action
(for non logged-in users) and wp_ajax_my_action
(for logged in users) hooks.
So one small step before going further is to make that path (wp-admin/admin-ajax.php
) accessible in your JavaScript. First, create a js file that we'll use for the AJAX process in your theme folder, let's say his name will be ./js/categories-ajax.js
. Then, add the following in your functions.php in order to enqueue this new script and make the path accessible through script localization:
add_action('wp_enqueue_scripts', 'ajax_categories_enqueue_scripts');
function ajax_categories_enqueue_scripts() {
wp_register_script('categories_ajax', get_stylesheet_directory_uri() . '/js/categories-ajax.js', array('jquery'), '', true);
wp_localize_script('categories_ajax', 'ls', array(
'ajax_url' => admin_url('admin-ajax.php')
));
wp_enqueue_script('categories_ajax');
}
With this the admin-ajax.php path will be accessible in your JS with ls.ajax_url
.
3. Trigger the ajax request
It's time to create the showPostsFromCategory()
function. So let's write in your new categories-ajax.js file. Personally, to avoid any possible conflict with jQuery in my plugin/theme developments I like to always encapsulate my jQuery code in a JavaScript closure and make the functions accessible through global vars like this:
(function($){
showPostsFromCategory = function(category_slug) {
// code function...
};
})(jQuery);
var showPostsFromCategory;
So I'll assume by now that the function code is inside a closure.
Basically, what we need to do now is to set up a $.post
request to admin-ajax.php that will sent the following parameters:
action
: an identifier of the AJAX action that is being called, in order to make Wordpress know which function should be called later. We'll name this dynamic_categories
.
category
: the category slug that have being clicked in the menu.
So the function code will look like this:
showPostsFromCategory = function(category_slug) {
$.post(ls.ajax_url, {action: 'dynamic_categories', category: category_slug}, function success(data) {
$('#your-post-content-wrapper').html(data); // Update the page with the new posts -- change this ID to the correct one
});
return false; // this is to cancel the link click, so the page isn't being redirected
}
This is very basic, you may want to add later some error handling and a loader. Here we just replace the actual posts with the AJAX return, that we suppose to be HTML. Nothing more to be done on the JS side, so let's work on the PHP side now.
4. Get the posts for the requested categories
First, add the following lines in your functions.php in order to attach the function get_categories_posts() to the wp_ajax_dynamic_categories
and wp_ajax_nopriv_dynamic_categories
actions.
add_action('wp_ajax_dynamic_categories', 'get_categories_posts');
add_action('wp_ajax_nopriv_dynamic_categories', 'get_categories_posts');
Now, we need to do the following in the function:
Check if the category slug is valid and it have posts associated, or display an error otherwise.
Get the posts associated to the category identified by the slug being sent by the JS script.
Loop through the posts and display each using a template part. For this, create a template in your theme folder that will contain only the necessary HTML/PHP to display one post. You can see an example of such template in the default wordpress theme (twentysixteen) in
template-parts/content.php
. Do the same kind of file in your theme - maybe there is already one.
You will need to update your archive template to make use of the template part too. This is very easy, remove the code used to display the post inside the loop and replace it with:
<?php get_template_part('template-parts/content'); ?>
So the whole function code will look like this:
function get_categories_posts() {
// Check if the category slug provided is ok and if it have posts
if(!isset($_POST['category'])) {
die('<p class="error">' . __('No category parameter provided', 'your-theme-slug') . '</p>');
}
$category_slug = sanitize_text_field($_POST['category']);
$category = get_category_by_slug($category_slug);
if(!$category) {
die('<p class="error">' . sprintf(__('Category %s not found', 'your-theme-slug'), $category_slug) . '</p>');
}
$posts = get_posts(array(
'category' => $category->term_id,
'posts_per_page' => -1 // get all posts
));
if(empty($posts)) {
die('<p class="error">' . sprintf(__('The category %s is empty', 'your-theme-slug'), $category->name) . '</p>');
}
// Loop through the posts and display them
foreach ($posts as $post) {
setup_postdata($post);
get_template_part('template-parts/content');
}
die('');
}
And, that's all! With this you now have a dynamic navigation for your posts categories.
For reference: sanitize_text_field
, get_category_by_slug
, get_posts
, setup_postdata
and get_template_part
.
Please note that this does not support pagination. If you need this, check this answer I made about this matter.
Further readings