WordPress: Build a Modal To Dynamically Populate From A Custom Post Type Using Ajax

One of the major projects that I’ve worked on in the last year is building out an acronym library using a custom post type (CPT) on Martech Zone. I’ve defined over 1,000 marketing, sales, and technology-related acronyms on the site! It’s been quite popular and drives much engagement with readers and search engine referrals.
While I’m happy with the engagement, one issue bothered me. When readers clicked on an acronym, it took them to an acronym page to reveal the definition… away from the article they were reading. That’s not an optimal user experience (UX). Today, I modified my child theme with a nice customization. Now, when you click on an acronym, the definition appears in a nice modal window that’s easy to dismiss.
Modal With Custom Post Type
Here’s what the results look like… you can try it out yourself by clicking the UX acronym.
What’s unique about this solution is that I can publish my article as I normally would, without adding any special code in the article or page to display the modal. Using jQuery, I capture when a user clicks on an anchor text, check the permalink structure, and if it’s an acronym, I use Ajax to query WordPress and populate the modal with the resulting content.
As always, I’ll provide the code here so you can figure out how to implement a similar solution on your WordPress site!
Step 1: Add the CSS for the Modal in Your Child Theme
You’ll likely want to modify your CSS from mine, I wanted to incorporate a dark or light skin into it and customize the widths for different screen sizes.
.modal {
display:none;
position:fixed;
z-index:1000;
left:0;
top:0;
width:100%;
height:100%;
overflow:auto;
background-color:rgb(0,0,0);
background-color:rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fff;
margin: 5% auto;
padding: 20px;
padding-right: 40px; /* Add space for the close button */
border: 1px solid #888;
border-radius: 20px;
width: 50%;
max-height: 80vh;
overflow-y: auto;
position: relative; /* Make position relative for absolute positioning of children */
}
.dark-skin .modal-content {
background-color: #111;
border: 1px solid #000;
}
@media screen and (min-width: 768px) and (max-width: 1024px) {
.modal-content {
width: 70%;
}
}
@media screen and (max-width: 767px) {
.modal-content {
width: 90%;
}
}
.modal-close-btn {
position: absolute;
top: 10px;
right: 15px;
font-size: 28px;
font-weight: bold;
}
.modal-close-btn:hover,
.modal-close-btn:focus {
cursor: pointer;
text-decoration: none;
}
Step 2: Add the Modal HTML To Your Footer
Directly above my </body>
tag in my footer.php
page, I add my empty Modal. Included in this is my close button, which is a simple HTML entity for an X.
<div id="customModal" class="modal">
<div class="modal-content">
<span class="modal-close-btn">×</span>
<div class="entry-content entry clearfix"></div>
</div>
</div>
Step 3: Add the Ajax Function and Custom Post Type Query To Your Child Theme
There are two functions below. modal_script
adds a listener for anyone clicking on a link. There’s a regex rule where I can look for /acronym/ within the URL and then return the slug after the link… which happens to be the acronym. That’s posted back to WordPress, and the custom post type is queried and returns the content in JSON, where it’s parsed and displayed in the modal HTML.
function modal_script() {
?>
<script type="text/javascript">
jQuery(document).ready(function(jQuery) {
jQuery(document).on('mouseenter', 'a', function(e) {
var href = jQuery(this).attr('href');
if (href.includes('/acronym/')) {
jQuery(this).addClass('acronym');
}
});
jQuery(document).on('click', 'a', function(e) {
var href = jQuery(this).attr('href');
if (href.includes('/acronym/')) {
e.preventDefault();
var acronym;
try {
var match = href.match(/acronym\/(.+)\/$/);
if (match) {
acronym = match[1];
} else {
console.error("No valid acronym found in href.");
return;
}
} catch (error) {
console.error("Error extracting acronym from href: ", error);
return;
}
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
method: 'POST',
dataType: 'json',
data: {
action: 'fetch_acronym_definition',
acronym: acronym,
nonce: '<?php echo wp_create_nonce('ajax_nonce'); ?>'
},
success: function(response) {
jQuery('#customModal .modal-content .entry-content').html('<h1>' + response.title + '</h1>' + '<h2>' + response.definition + '</h2>' + response.content);
jQuery('#customModal').show();
}
});
}
});
jQuery('.modal-close-btn').on('click', function() {
jQuery('#customModal').hide();
});
jQuery(window).on('click', function(e) {
if (jQuery(e.target).is('.modal')) {
jQuery('#customModal').hide();
}
});
});
</script>
<?php
}
add_action('wp_footer', 'modal_script');
This script adds an acronym class to my links so that I can customize how they appear in relation to my normal links on the site.
function fetch_acronym_definition() {
// Check nonce for security
check_ajax_referer('ajax_nonce', 'nonce');
$acronym_slug = sanitize_text_field($_POST['acronym']);
$args = array(
'name' => $acronym_slug,
'post_type' => 'acronym',
'post_status' => 'publish',
'numberposts' => 1
);
$post = get_posts($args);
if (!empty($post)) {
$post_id = $post[0]->ID;
$post_title = $post[0]->post_title;
$acronym_definition = get_post_meta($post_id, 'acronym_definition', true);
$content = apply_filters('the_content', $post[0]->post_content); // Get the content and apply content filters
// Prepare and send JSON response
wp_send_json(array(
'title' => $post_title,
'definition' => $acronym_definition,
'content' => $content
));
} else {
echo 'Definition not found.';
}
wp_die(); // This is required to terminate immediately and return a proper response
}
add_action('wp_ajax_fetch_acronym_definition', 'fetch_acronym_definition');
add_action('wp_ajax_nopriv_fetch_acronym_definition', 'fetch_acronym_definition');
Pretty efficient solution! The only disadvantage to this solution is that it will drop my pageviews per session. However, since it’s enhancing my reader’s experience, the advantages far outweigh this!