Custom Product Tabs AJAX
August 7, 2025 · AI Conversations
<script>
document.addEventListener('DOMContentLoaded', function () {
const tabs = document.querySelectorAll('.tab-button');
const contents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', function () {
tabs.forEach(t => t.classList.remove('active'));
contents.forEach(c => c.style.display = 'none');
this.classList.add('active');
document.getElementById('tab-' + this.dataset.tab).style.display = 'block';
});
});
});
</script>
don't use short code just user custom code and ajax and apply owl and display 4 items one time
—
### ✅ PHP Part – Shortcode + AJAX Handler
Add this to your theme’s `functions.php` or a custom plugin:
add_shortcode('custom_product_tabs_slider', 'custom_product_tabs_slider_func');
function custom_product_tabs_slider_func() {
ob_start();
?>
<div class="product-tabs-wrapper">
<div class="product-tabs-nav">
<button class="tab-button active" data-tab="new">New</button>
<button class="tab-button" data-tab="sales">Sales</button>
<button class="tab-button" data-tab="bestsellers">Bestsellers</button>
<button class="tab-button" data-tab="spotlights">Spotlights</button>
</div>
<div class="tab-content-wrapper">
<div class="owl-carousel owl-theme" id="product-tab-slider"></div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const tabs = document.querySelectorAll('.tab-button');
const content = document.querySelector('#product-tab-slider');
function fetchProducts(tab) {
content.innerHTML = '<div class="loading">Loading...</div>';
fetch('<?php echo admin_url('admin-ajax.php'); ?>?action=load_products_tab&type=' + tab)
.then(res => res.text())
.then(html => {
content.innerHTML = html;
jQuery('#product-tab-slider').owlCarousel({
items: 4,
loop: false,
margin: 10,
nav: true,
dots: false,
responsive: {
0: { items: 1 },
600: { items: 2 },
1000: { items: 4 }
}
});
});
}
tabs.forEach(tab => {
tab.addEventListener('click', function () {
tabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
jQuery('#product-tab-slider').trigger('destroy.owl.carousel');
fetchProducts(this.dataset.tab);
});
});
// Load initial tab
fetchProducts('new');
});
</script>
<?php
return ob_get_clean();
}
// AJAX handler
add_action('wp_ajax_load_products_tab', 'ajax_load_products_tab');
add_action('wp_ajax_nopriv_load_products_tab', 'ajax_load_products_tab');
function ajax_load_products_tab() {
$type = $_GET['type'] ?? 'new';
$args = [
'post_type' => 'product',
'posts_per_page' => 8,
'post_status' => 'publish'
];
switch ($type) {
case 'sales':
$args['meta_query'][] = [
'key' => '_sale_price',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC'
];
break;
case 'bestsellers':
$args['orderby'] = 'meta_value_num';
$args['meta_key'] = 'total_sales';
break;
case 'spotlights':
$args['tax_query'][] = [
'taxonomy' => 'product_visibility',
'field' => 'name',
'terms' => 'featured',
'operator' => 'IN'
];
break;
case 'new':
default:
$args['orderby'] = 'date';
$args['order'] = 'DESC';
break;
}
$query = new WP_Query($args);
if ($query->have_posts()) :
while ($query->have_posts()): $query->the_post();
global $product;
?>
<div class="item">
<a href="<?php the_permalink(); ?>" class="product-card">
<?php echo woocommerce_get_product_thumbnail(); ?>
<h3><?php the_title(); ?></h3>
<span class="price"><?php echo $product->get_price_html(); ?></span>
</a>
</div>
<?php
endwhile;
wp_reset_postdata();
else:
echo '<p>No products found.</p>';
endif;
wp_die();
}
—
### ✅ Enqueue Owl Carousel Scripts
Add this to `functions.php`:
function enqueue_owl_assets() {
wp_enqueue_style('owl-carousel', 'https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.carousel.min.css');
wp_enqueue_style('owl-theme', 'https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.theme.default.min.css');
wp_enqueue_script('owl-carousel', 'https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/owl.carousel.min.js', ['jquery'], null, true);
}
add_action('wp_enqueue_scripts', 'enqueue_owl_assets');
—
### ✅ Use in Page Builder or Template
echo do_shortcode('[custom_product_tabs_slider]');
—
Let me know if you want autoplay or additional carousel settings.
Here’s the correct `WP_Query` arguments for `on_sale="true"` equivalent:
$args = [
'post_type' => 'product',
'posts_per_page' => 8,
'post_status' => 'publish',
'meta_query' => [
[
'key' => '_sale_price',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
]
]
];
### ✅ Optional: Combine with stock status
To exclude out-of-stock products, you can enhance the `meta_query`:
'meta_query' => [
'relation' => 'AND',
[
'key' => '_sale_price',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
],
[
'key' => '_stock_status',
'value' => 'instock'
]
]
### ✅ Optional: Add taxonomy filter if needed
To query only from specific categories:
'tax_query' => [
[
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => ['your-category-slug'],
]
]
Let me know if you want the complete integration in your tab-ajax slider.
<script>
document.addEventListener('DOMContentLoaded', function () {
const tabs = document.querySelectorAll('.tab-button');
const content = document.querySelector('#product-tab-slider');
function fetchProducts(tab) {
content.innerHTML = '<div class="loader"></div>';
fetch('<?php echo admin_url('admin-ajax.php'); ?>?action=load_products_tab&type=' + tab)
.then(res => res.text())
.then(html => {
content.innerHTML = html;
jQuery('#product-tab-slider').owlCarousel({
items: 4,
loop: false,
margin: 10,
nav: true,
dots: false,
responsive: {
0: { items: 1 },
600: { items: 2 },
1000: { items: 4 }
}
});
});
}
tabs.forEach(tab => {
tab.addEventListener('click', function () {
tabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
jQuery('#product-tab-slider').trigger('destroy.owl.carousel');
fetchProducts(this.dataset.tab);
});
});
// Load initial tab
fetchProducts('new');
});
</script>
add loader and apply smooth tab click result show
– ✅ A smooth fade-in effect on tab content load
– ✅ A proper **CSS spinner loader** while fetching
– ✅ Improved UX on tab click
—
### ✅ HTML + JS (Tab Markup with AJAX + Smooth UI)
<div class="product-tabs-wrapper">
<div class="product-tabs-nav">
<button class="tab-button active" data-tab="new"><?php echo __('New','flatsome')?></button>
<button class="tab-button" data-tab="sales"><?php echo __('Sales','flatsome')?></button>
<button class="tab-button" data-tab="bestsellers"><?php echo __('Bestsellers','flatsome')?></button>
<button class="tab-button" data-tab="spotlights"><?php echo __('Spotlights','flatsome')?></button>
</div>
<div class="tab-content-wrapper">
<div class="loader" style="display: none;"></div>
<div class="owl-carousel owl-theme" id="product-tab-slider"></div>
</div>
</div>
<style>
.product-tabs-wrapper {
margin-top: 20px;
}
.product-tabs-nav {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.tab-button {
padding: 10px 20px;
border: 1px solid #ccc;
background: #f2f2f2;
cursor: pointer;
border-radius: 4px;
transition: background 0.3s;
}
.tab-button.active {
background: #19bdc0;
color: #fff;
border-color: #19bdc0;
}
.loader {
border: 5px solid #f3f3f3;
border-top: 5px solid #19bdc0;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 0.8s linear infinite;
margin: 40px auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.owl-carousel .item {
opacity: 0;
transform: translateY(10px);
transition: all 0.3s ease-in-out;
}
.owl-carousel .item.loaded {
opacity: 1;
transform: translateY(0);
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function () {
const tabs = document.querySelectorAll('.tab-button');
const content = document.querySelector('#product-tab-slider');
const loader = document.querySelector('.loader');
function fetchProducts(tab) {
content.innerHTML = '';
loader.style.display = 'block';
fetch('<?php echo admin_url('admin-ajax.php'); ?>?action=load_products_tab&type=' + tab)
.then(res => res.text())
.then(html => {
loader.style.display = 'none';
content.innerHTML = html;
const items = content.querySelectorAll('.item');
items.forEach((el, i) => {
setTimeout(() => el.classList.add('loaded'), i * 100);
});
jQuery('#product-tab-slider').owlCarousel({
items: 4,
loop: false,
margin: 10,
nav: true,
dots: false,
responsive: {
0: { items: 1 },
600: { items: 2 },
1000: { items: 4 }
}
});
});
}
tabs.forEach(tab => {
tab.addEventListener('click', function () {
tabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
jQuery('#product-tab-slider').trigger('destroy.owl.carousel');
fetchProducts(this.dataset.tab);
});
});
// Load initial tab
fetchProducts('new');
});
</script>
—
### ✅ AJAX Output HTML Format (in PHP handler)
Make sure your `load_products_tab` handler returns items like:
echo '<div class="item"><a href="' . get_permalink() . '" class="product-card">';
echo woocommerce_get_product_thumbnail();
echo '<h3>' . get_the_title() . '</h3>';
echo '<span class="price">' . $product->get_price_html() . '</span>';
echo '</a></div>';
Add the class `.item` so each slide fades in smoothly with `.loaded`.
—
Let me know if you want **autoplay**, **lazy load**, or **custom layout per tab**.
—
### ✅ PHP Code: Show default image if none is set
<?php
if (has_post_thumbnail()) {
echo woocommerce_get_product_thumbnail();
} else {
echo '<img src="' . esc_url(get_template_directory_uri() . '/assets/images/default-product.jpg') . '" alt="Default Product" />';
}
?>
—
### 📝 Notes:
– Replace `/assets/images/default-product.jpg` with the actual path to your fallback image.
– You can also use `get_stylesheet_directory_uri()` if the image is in a **child theme**.
—
### ✅ Optional: Global fallback image in `functions.php`
If you want to set a fallback image globally across WooCommerce:
add_filter('woocommerce_placeholder_img_src', 'custom_woocommerce_placeholder_img_src');
function custom_woocommerce_placeholder_img_src($src) {
return get_template_directory_uri() . '/assets/images/default-product.jpg';
}
This automatically replaces the default WooCommerce placeholder.
—
Let me know if you want to apply this fallback for product loop thumbnails or specific WooCommerce hooks.
—
### ✅ CSS Update – Underline for Active Tab
Add this inside your “ block:
.tab-button.active {
position: relative;
}
.tab-button.active::after {
content: "";
position: absolute;
bottom: -5px;
left: 0;
height: 3px;
width: 100%;
background-color: #19bdc0; /* Adjust to your brand color */
border-radius: 2px;
}
—
### ✅ Result
– Adds a **3px thick colored underline** directly under the active tab.
– Positioned smoothly with some space (`bottom: -5px`) so it doesn’t touch text.
—
Let me know if you want a smooth transition effect or animation too.
To handle this, you need to **check if the image file exists** in the filesystem, not just if the thumbnail ID is set.
—
### ✅ Full Code: Fallback if file missing
<?php
$thumbnail_id = get_post_thumbnail_id();
$image_src = wp_get_attachment_image_src($thumbnail_id, 'medium');
if ($image_src && file_exists(get_attached_file($thumbnail_id))) {
echo '<img src="' . esc_url($image_src[0]) . '" alt="' . esc_attr(get_the_title()) . '">';
} else {
echo '<img src="' . esc_url(DEFAULT_IMG) . '" alt="' . esc_attr(get_the_title()) . '">';
}
?>
—
### ✅ Notes:
– `get_attached_file($thumbnail_id)` returns the **full path** to the image file on the server.
– `file_exists()` ensures the file **actually exists**, not just in the DB.
– `DEFAULT_IMG` should be a full URL like:
define('DEFAULT_IMG', get_template_directory_uri() . '/assets/images/default-product.jpg');
—
Let me know if you also want to log broken images or replace missing ones in bulk.
– `has_post_thumbnail()` returns `true`
– `wp_get_attachment_image_src()` gives a URL
– BUT the file isn't physically there, so the image is broken
### ✅ Use this **robust fallback** code:
<?php
$thumbnail_id = get_post_thumbnail_id();
if ($thumbnail_id) {
$image_path = get_attached_file($thumbnail_id); // Absolute path on disk
$image_src = wp_get_attachment_image_src($thumbnail_id, 'medium'); // Get URL
if ($image_src && file_exists($image_path)) {
echo '<img src="' . esc_url($image_src[0]) . '" alt="' . esc_attr(get_the_title()) . '">';
} else {
echo '<img src="' . esc_url(get_template_directory_uri() . '/assets/images/default-product.jpg') . '" alt="' . esc_attr(get_the_title()) . '">';
}
} else {
echo '<img src="' . esc_url(get_template_directory_uri() . '/assets/images/default-product.jpg') . '" alt="' . esc_attr(get_the_title()) . '">';
}
?>
—
### ✅ Checklist:
1. Make sure this path is correct and the file actually exists:
your-theme/assets/images/default-product.jpg
2. If using a **child theme**, change:
get_template_directory_uri()
to:
get_stylesheet_directory_uri()
—
### 💡 Tip:
To test this, temporarily rename a file in `/uploads` that’s linked to a product’s featured image and reload.
Let me know if you want to **auto-replace broken image links in the database**, or show a WooCommerce product fallback image globally.