@mayurgodhani/ecomtools-cli
Version:
E-commerce tools MCP server for Shopify development
694 lines (596 loc) • 20.3 kB
Plain Text
Title: Shopify Product Quick View
Description: A lightweight product quick view modal that displays product details without leaving the current page.
Code:
<!-- Product Quick View Modal HTML -->
<div id="quick-view-modal" class="quick-view-modal">
<div class="quick-view-container">
<div class="quick-view-close">×</div>
<div class="quick-view-content">
<div class="quick-view-loader">
<div class="spinner"></div>
</div>
<div class="quick-view-product-content" id="quick-view-product-content"></div>
</div>
</div>
</div>
<!-- Product Quick View Modal Content Template -->
<template id="quick-view-template">
<div class="quick-view-product">
<div class="quick-view-product-gallery">
<div class="quick-view-product-image">
<img src="" alt="" class="quick-view-featured-image">
</div>
<div class="quick-view-thumbnails"></div>
</div>
<div class="quick-view-product-details">
<h2 class="quick-view-product-title"></h2>
<div class="quick-view-product-price"></div>
<div class="quick-view-product-description"></div>
<form action="/cart/add" method="post" class="quick-view-product-form">
<input type="hidden" name="id" value="" class="quick-view-variant-id">
<div class="quick-view-product-options"></div>
<div class="quick-view-quantity-wrapper">
<label for="quick-view-quantity">Quantity</label>
<div class="quick-view-quantity-input">
<button type="button" class="quick-view-quantity-decrease">-</button>
<input type="number" id="quick-view-quantity" name="quantity" value="1" min="1">
<button type="button" class="quick-view-quantity-increase">+</button>
</div>
</div>
<div class="quick-view-submit">
<button type="submit" class="quick-view-add-to-cart">Add to Cart</button>
</div>
<div class="quick-view-view-details">
<a href="" class="quick-view-product-link">View Full Details</a>
</div>
</form>
</div>
</div>
</template>
<!-- Product Quick View CSS -->
<style>
.quick-view-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
overflow-y: auto;
}
.quick-view-container {
position: relative;
width: 90%;
max-width: 1000px;
margin: 50px auto;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
}
.quick-view-close {
position: absolute;
top: 10px;
right: 15px;
font-size: 28px;
font-weight: 700;
color: #333;
cursor: pointer;
z-index: 10;
}
.quick-view-content {
padding: 20px;
position: relative;
min-height: 200px;
}
.quick-view-loader {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.9);
z-index: 5;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #333;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.quick-view-product {
display: flex;
flex-wrap: wrap;
}
.quick-view-product-gallery {
width: 50%;
padding-right: 20px;
}
.quick-view-product-details {
width: 50%;
}
.quick-view-product-image {
margin-bottom: 15px;
}
.quick-view-featured-image {
width: 100%;
height: auto;
}
.quick-view-thumbnails {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.quick-view-thumbnail {
width: 60px;
height: 60px;
border: 1px solid #ddd;
cursor: pointer;
overflow: hidden;
}
.quick-view-thumbnail.active {
border-color: #000;
}
.quick-view-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.quick-view-product-title {
margin-top: 0;
margin-bottom: 10px;
font-size: 24px;
}
.quick-view-product-price {
font-size: 18px;
font-weight: 600;
margin-bottom: 15px;
}
.quick-view-product-description {
margin-bottom: 20px;
font-size: 14px;
line-height: 1.6;
}
.quick-view-product-options {
margin-bottom: 20px;
}
.quick-view-option-selector {
margin-bottom: 15px;
}
.quick-view-option-selector label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.quick-view-option-selector select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.quick-view-quantity-wrapper {
margin-bottom: 20px;
}
.quick-view-quantity-wrapper label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.quick-view-quantity-input {
display: flex;
align-items: center;
width: fit-content;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
.quick-view-quantity-input button {
width: 30px;
height: 38px;
background: none;
border: none;
cursor: pointer;
font-size: 16px;
}
.quick-view-quantity-input input {
width: 50px;
height: 38px;
border: none;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
text-align: center;
-moz-appearance: textfield;
}
.quick-view-quantity-input input::-webkit-outer-spin-button,
.quick-view-quantity-input input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.quick-view-add-to-cart {
width: 100%;
padding: 12px;
background-color: #000;
color: #fff;
border: none;
border-radius: 4px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.3s;
}
.quick-view-add-to-cart:hover {
background-color: #333;
}
.quick-view-add-to-cart.disabled {
background-color: #ccc;
cursor: not-allowed;
}
.quick-view-view-details {
margin-top: 15px;
text-align: center;
}
.quick-view-product-link {
color: #333;
text-decoration: underline;
}
@media (max-width: 768px) {
.quick-view-product-gallery,
.quick-view-product-details {
width: 100%;
padding-right: 0;
}
.quick-view-product-gallery {
margin-bottom: 20px;
}
}
</style>
<!-- Quick View JS -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const quickViewButtons = document.querySelectorAll('[data-quick-view]');
const quickViewModal = document.getElementById('quick-view-modal');
const quickViewClose = document.querySelector('.quick-view-close');
const quickViewContent = document.getElementById('quick-view-product-content');
const quickViewLoader = document.querySelector('.quick-view-loader');
const quickViewTemplate = document.getElementById('quick-view-template');
// Add quick view buttons to product cards
quickViewButtons.forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault();
const productHandle = this.getAttribute('data-product-handle');
openQuickView(productHandle);
});
});
// Close modal when clicking the X
if (quickViewClose) {
quickViewClose.addEventListener('click', closeQuickView);
}
// Close modal when clicking outside of content
quickViewModal.addEventListener('click', function(e) {
if (e.target === quickViewModal) {
closeQuickView();
}
});
// Close modal with ESC key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && quickViewModal.style.display === 'block') {
closeQuickView();
}
});
// Open quick view modal and load product
function openQuickView(productHandle) {
// Show modal
quickViewModal.style.display = 'block';
document.body.style.overflow = 'hidden'; // Prevent scrolling
// Show loader
quickViewLoader.style.display = 'flex';
quickViewContent.innerHTML = '';
// Fetch product data
fetch(`/products/${productHandle}?view=quick-view`)
.then(response => response.text())
.then(html => {
// If the response is HTML (using a custom template)
loadProductHTML(html);
})
.catch(error => {
// If the custom template doesn't exist, fetch JSON data
fetch(`/products/${productHandle}.js`)
.then(response => response.json())
.then(product => {
loadProductJSON(product);
})
.catch(error => {
console.error('Error loading product:', error);
quickViewContent.innerHTML = '<p>Error loading product. Please try again.</p>';
quickViewLoader.style.display = 'none';
});
});
}
// Load product from HTML template
function loadProductHTML(html) {
quickViewContent.innerHTML = html;
initializeQuickView();
quickViewLoader.style.display = 'none';
}
// Load product from JSON data
function loadProductJSON(product) {
// Clone the template
const content = quickViewTemplate.content.cloneNode(true);
// Set product data
content.querySelector('.quick-view-product-title').textContent = product.title;
content.querySelector('.quick-view-product-price').innerHTML = formatMoney(product.price);
content.querySelector('.quick-view-product-description').innerHTML = product.description;
content.querySelector('.quick-view-product-link').href = `/products/${product.handle}`;
// Set featured image
if (product.featured_image) {
const featuredImage = content.querySelector('.quick-view-featured-image');
featuredImage.src = product.featured_image;
featuredImage.alt = product.title;
}
// Add thumbnail images
const thumbnailsContainer = content.querySelector('.quick-view-thumbnails');
product.images.forEach((image, index) => {
const thumbnail = document.createElement('div');
thumbnail.className = 'quick-view-thumbnail';
if (index === 0) thumbnail.classList.add('active');
const img = document.createElement('img');
img.src = image;
img.alt = `${product.title} - Image ${index + 1}`;
thumbnail.appendChild(img);
thumbnail.addEventListener('click', function() {
// Set new featured image
content.querySelector('.quick-view-featured-image').src = image;
// Update active thumbnail
content.querySelectorAll('.quick-view-thumbnail').forEach(thumb => {
thumb.classList.remove('active');
});
this.classList.add('active');
});
thumbnailsContainer.appendChild(thumbnail);
});
// Add variant options
if (product.options_with_values && product.options_with_values.length > 0) {
const optionsContainer = content.querySelector('.quick-view-product-options');
product.options_with_values.forEach(option => {
const optionSelector = document.createElement('div');
optionSelector.className = 'quick-view-option-selector';
const label = document.createElement('label');
label.textContent = option.name;
label.htmlFor = `quick-view-option-${option.name.toLowerCase().replace(/\s+/g, '-')}`;
const select = document.createElement('select');
select.id = `quick-view-option-${option.name.toLowerCase().replace(/\s+/g, '-')}`;
select.className = 'quick-view-option-select';
select.dataset.option = option.position;
option.values.forEach(value => {
const optionEl = document.createElement('option');
optionEl.value = value;
optionEl.textContent = value;
select.appendChild(optionEl);
});
optionSelector.appendChild(label);
optionSelector.appendChild(select);
optionsContainer.appendChild(optionSelector);
});
// Add event listeners to update variant ID
content.querySelectorAll('.quick-view-option-select').forEach(select => {
select.addEventListener('change', function() {
updateVariantSelection(content, product);
});
});
// Initialize variant selection
updateVariantSelection(content, product);
} else {
// Single variant - set the variant ID directly
content.querySelector('.quick-view-variant-id').value = product.variants[0].id;
}
// Add quantity buttons functionality
const quantityInput = content.querySelector('#quick-view-quantity');
content.querySelector('.quick-view-quantity-decrease').addEventListener('click', function() {
if (quantityInput.value > 1) {
quantityInput.value = parseInt(quantityInput.value) - 1;
}
});
content.querySelector('.quick-view-quantity-increase').addEventListener('click', function() {
quantityInput.value = parseInt(quantityInput.value) + 1;
});
// Add to cart functionality
const addToCartForm = content.querySelector('.quick-view-product-form');
addToCartForm.addEventListener('submit', function(e) {
e.preventDefault();
const addToCartButton = this.querySelector('.quick-view-add-to-cart');
const originalText = addToCartButton.textContent;
// Disable button during submission
addToCartButton.disabled = true;
addToCartButton.textContent = 'Adding...';
// Prepare form data
const formData = new FormData(this);
// Add to cart via AJAX
fetch('/cart/add.js', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
addToCartButton.textContent = 'Added!';
// Update cart count or show mini cart
updateCartCount();
// Close quick view after delay
setTimeout(() => {
closeQuickView();
addToCartButton.disabled = false;
addToCartButton.textContent = originalText;
}, 1500);
})
.catch(error => {
console.error('Error adding to cart:', error);
addToCartButton.textContent = 'Error - Try Again';
setTimeout(() => {
addToCartButton.disabled = false;
addToCartButton.textContent = originalText;
}, 1500);
});
});
// Add content to modal
quickViewContent.appendChild(content);
quickViewLoader.style.display = 'none';
}
// Update variant selection based on option selects
function updateVariantSelection(content, product) {
// Get selected options
const selectedOptions = [];
content.querySelectorAll('.quick-view-option-select').forEach(select => {
selectedOptions[parseInt(select.dataset.option) - 1] = select.value;
});
// Find matching variant
let matchingVariant = null;
for (const variant of product.variants) {
if (variant.options.every((option, index) => option === selectedOptions[index])) {
matchingVariant = variant;
break;
}
}
if (matchingVariant) {
// Update variant ID and price
content.querySelector('.quick-view-variant-id').value = matchingVariant.id;
if (matchingVariant.available) {
content.querySelector('.quick-view-add-to-cart').disabled = false;
content.querySelector('.quick-view-add-to-cart').textContent = 'Add to Cart';
content.querySelector('.quick-view-add-to-cart').classList.remove('disabled');
} else {
content.querySelector('.quick-view-add-to-cart').disabled = true;
content.querySelector('.quick-view-add-to-cart').textContent = 'Sold Out';
content.querySelector('.quick-view-add-to-cart').classList.add('disabled');
}
// Update price if different
if (matchingVariant.price !== product.price) {
content.querySelector('.quick-view-product-price').innerHTML = formatMoney(matchingVariant.price);
}
}
}
// Initialize quick view functionality after HTML is loaded
function initializeQuickView() {
// Add functionality to quantity buttons
const quantityInput = quickViewContent.querySelector('#quick-view-quantity');
if (quantityInput) {
quickViewContent.querySelector('.quick-view-quantity-decrease')?.addEventListener('click', function() {
if (quantityInput.value > 1) {
quantityInput.value = parseInt(quantityInput.value) - 1;
}
});
quickViewContent.querySelector('.quick-view-quantity-increase')?.addEventListener('click', function() {
quantityInput.value = parseInt(quantityInput.value) + 1;
});
}
// Add functionality to thumbnails
const thumbnails = quickViewContent.querySelectorAll('.quick-view-thumbnail');
thumbnails.forEach(thumbnail => {
thumbnail.addEventListener('click', function() {
const newSrc = this.querySelector('img').src;
quickViewContent.querySelector('.quick-view-featured-image').src = newSrc;
thumbnails.forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
// Add to cart functionality
const addToCartForm = quickViewContent.querySelector('.quick-view-product-form');
if (addToCartForm) {
addToCartForm.addEventListener('submit', function(e) {
e.preventDefault();
const addToCartButton = this.querySelector('.quick-view-add-to-cart');
const originalText = addToCartButton.textContent;
// Disable button during submission
addToCartButton.disabled = true;
addToCartButton.textContent = 'Adding...';
// Add to cart via AJAX
fetch('/cart/add.js', {
method: 'POST',
body: new FormData(this),
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
addToCartButton.textContent = 'Added!';
// Update cart count or show mini cart
updateCartCount();
setTimeout(() => {
closeQuickView();
addToCartButton.disabled = false;
addToCartButton.textContent = originalText;
}, 1500);
})
.catch(error => {
console.error('Error adding to cart:', error);
addToCartButton.textContent = 'Error - Try Again';
setTimeout(() => {
addToCartButton.disabled = false;
addToCartButton.textContent = originalText;
}, 1500);
});
});
}
// Add variant changing functionality
const variantSelectors = quickViewContent.querySelectorAll('.quick-view-option-select');
if (variantSelectors.length) {
variantSelectors.forEach(selector => {
selector.addEventListener('change', function() {
// Update product variant
// This functionality would depend on your specific Shopify theme
});
});
}
}
// Close quick view modal
function closeQuickView() {
quickViewModal.style.display = 'none';
document.body.style.overflow = '';
quickViewContent.innerHTML = '';
}
// Format money value
function formatMoney(cents) {
const currency = window.Shopify?.currency?.active || 'USD';
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
minimumFractionDigits: 2
});
return formatter.format(cents / 100);
}
// Update cart count (depends on theme structure)
function updateCartCount() {
fetch('/cart.js')
.then(response => response.json())
.then(cart => {
const cartCountElements = document.querySelectorAll('.cart-count');
cartCountElements.forEach(element => {
element.textContent = cart.item_count;
});
})
.catch(error => {
console.error('Error updating cart count:', error);
});
}
});
</script>
<!-- Add these quick view buttons to your collection template or product card snippet -->
<!--
<button class="quick-view-button" data-quick-view data-product-handle="{{ product.handle }}">
Quick View
</button>
-->
<!-- Add a special quick-view template to your theme -->
<!--
Create a template at templates/product.quick-view.liquid with the product content
you want to display in the quick view modal
-->