Collection Page Buttons
Add wishlist buttons to product cards on collection and listing pages using the Swym JS SDK.
Collection Page Buttons
Inject a wishlist button into every product card on collection/listing pages. Handles dynamic content (pagination, filtering, infinite scroll) via MutationObserver.
Theme-Specific: CSS selectors for product cards vary by theme. Update the
SELECTORSconfig below to match your theme. Inspect your theme's product grid in DevTools to find the correct selectors.
Where to Add the Code
- Create an asset file: In your theme, go to Assets > Add new asset > create
swym-collection-buttons.js - Include it in your collection section: Open
templates/collection.json→ find the main section type (e.g.,main-collectionin Dawn,collection-gridin Horizon) → edit that section file and add the script tag at the bottom
Liquid — Add to your collection section
{% comment %}
Swym Collection Wishlist Buttons — SDK-Only Mode
Include this at the bottom of your collection template.
{% endcomment %}
<script src="{{ 'swym-collection-buttons.js' | asset_url }}" defer></script>JavaScript — swym-collection-buttons.js
swym-collection-buttons.js(function() {
'use strict';
// --- CONFIGURATION: Update these selectors to match your theme ---
var SELECTORS = {
productCard: '.card-wrapper, .card--product, .product-card, .grid__item',
productLink: 'a[href*="/products/"]',
productGrid: '#product-grid, .collection-products, .grid',
imageWrapper: '.card__media, .media, .product-card__media, .product-card__image'
};
var wishlistedSet = {};
function initCollectionButtons(swat) {
swat.fetch(function(wishlisted) {
wishlistedSet = {};
wishlisted.forEach(function(item) {
wishlistedSet[item.empi] = true;
});
injectButtons(swat);
observeGrid(swat);
});
}
function injectButtons(swat) {
var cards = document.querySelectorAll(SELECTORS.productCard);
cards.forEach(function(card) {
if (card.querySelector('.swym-collection-btn')) return;
var link = card.querySelector(SELECTORS.productLink);
if (!link) return;
var href = link.getAttribute('href');
if (!href) return;
// Extract clean product URL and handle
var rawUrl = href.startsWith('http') ? href : window.location.origin + href;
var productUrl = rawUrl.split('?')[0];
var handleMatch = productUrl.match(/\/products\/([^/?#]+)/);
var productHandle = handleMatch ? handleMatch[1] : '';
if (!productHandle) return;
// Get product ID from DOM for initial state check (theme-dependent)
var productId = 0;
var heading = card.querySelector('[id^="title-"], [id^="CardLink-"]');
if (heading) {
var idParts = heading.id.split('-');
var lastPart = idParts[idParts.length - 1];
if (lastPart && !isNaN(lastPart)) productId = Number(lastPart);
}
var btn = document.createElement('button');
btn.className = 'swym-collection-btn';
btn.setAttribute('type', 'button');
btn.setAttribute('aria-label', 'Add to wishlist');
btn.textContent = '♡'; // Replace with your icon (SVG, image, etc.)
if (productId && wishlistedSet[productId]) {
btn.classList.add('swym-added');
btn.setAttribute('aria-label', 'Remove from wishlist');
}
btn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
// Use getProductDetails to get correct empi + epi (first variant ID)
// This is how Swym's own collection implementation works
swat.getProductDetails({ du: '/products/' + productHandle }, function(productJson) {
var p = {
empi: productJson.empi || productJson.id,
epi: productJson.epi || (productJson.variants && productJson.variants[0]
? productJson.variants[0].id : 0),
du: productUrl,
dt: productJson.dt || productJson.title || '',
iu: productJson.iu || '',
pr: productJson.pr || 0
};
if (btn.classList.contains('swym-added')) {
swat.removeFromWishList(p, function() {
btn.classList.remove('swym-added');
btn.setAttribute('aria-label', 'Add to wishlist');
delete wishlistedSet[p.empi];
});
} else {
swat.addToWishList(p, function() {
btn.classList.add('swym-added');
btn.setAttribute('aria-label', 'Remove from wishlist');
wishlistedSet[p.empi] = true;
});
}
});
});
// Position inside image wrapper for proper alignment across card heights
var imageWrapper = card.querySelector(SELECTORS.imageWrapper);
if (imageWrapper) {
if (getComputedStyle(imageWrapper).position === 'static') {
imageWrapper.style.position = 'relative';
}
imageWrapper.appendChild(btn);
} else {
if (getComputedStyle(card).position === 'static') {
card.style.position = 'relative';
}
card.appendChild(btn);
}
});
}
function observeGrid(swat) {
var grid = document.querySelector(SELECTORS.productGrid);
if (!grid) return;
var observer = new MutationObserver(function() {
injectButtons(swat);
});
observer.observe(grid, { childList: true, subtree: true });
}
window.SwymCallbacks = window.SwymCallbacks || [];
window.SwymCallbacks.push(initCollectionButtons);
})();How It Works
- On SDK ready,
swat.fetch()gets all wishlisted products → builds a lookup byempi - Queries all product cards and injects a button into each
- On click, calls
swat.getProductDetails({ du: '/products/HANDLE' })to get the correct product data with the proper variant ID — this is how Swym's own collection button implementation works MutationObserverwatches the grid container — re-injects buttons when the DOM changes (pagination, filtering, infinite scroll)
Why
getProductDetails? On collection pages, variant IDs aren't in the DOM (Dawn lazy-loads quick-add forms). Usingepi = empi(product ID as variant ID) causes a "Unknown product" error from the Swym API.getProductDetailsfetches the correct first variant ID via the SDK.
Finding Your Theme's Selectors
Open your collection page in DevTools and inspect a product card:
| Theme | Product Card | Product Link | Grid Container | Image Wrapper |
|---|---|---|---|---|
| Dawn | .card-wrapper | a[href*="/products/"] | #product-grid | .card__media, .media |
| Ritual | .product-card | a[href*="/products/"] | .collection__products | .product-card__media |
| Prestige | .product-item | .product-item__link | .product-list | .product-item__image-wrapper |
| Debut | .grid-product | .grid-product__link | .collection-products | .grid-product__image-wrapper |
| Custom | Inspect the DOM | Look for <a> with /products/ href | Parent of product cards | Element wrapping <img> |
Styling
Each button gets swym-added when wishlisted. The default text is ♡ — replace with your own icon (SVG, image, etc.). Position and style the buttons to match your theme's product cards.
Notes
e.preventDefault()+e.stopPropagation()are both required on click — the button is inside a product card link- The button skips cards that already have a
.swym-collection-btnto prevent duplicates - Product URL query params (referral tracking like
?pr_prod_strat=...) are stripped — use the clean canonical URL fordu - Alignment: Always position the wishlist button inside the image wrapper element, not the card root. Cards have varying title/price heights — placing the button on the card root causes misalignment across the grid. The image wrapper has consistent dimensions.
Updated about 8 hours ago