API ReferenceGuides
Log In
API Reference

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 SELECTORS config below to match your theme. Inspect your theme's product grid in DevTools to find the correct selectors.

Where to Add the Code

  1. Create an asset file: In your theme, go to Assets > Add new asset > create swym-collection-buttons.js
  2. Include it in your collection section: Open templates/collection.json → find the main section type (e.g., main-collection in Dawn, collection-grid in 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

(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

  1. On SDK ready, swat.fetch() gets all wishlisted products → builds a lookup by empi
  2. Queries all product cards and injects a button into each
  3. 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
  4. MutationObserver watches 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). Using epi = empi (product ID as variant ID) causes a "Unknown product" error from the Swym API. getProductDetails fetches the correct first variant ID via the SDK.

Finding Your Theme's Selectors

Open your collection page in DevTools and inspect a product card:

ThemeProduct CardProduct LinkGrid ContainerImage Wrapper
Dawn.card-wrappera[href*="/products/"]#product-grid.card__media, .media
Ritual.product-carda[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
CustomInspect the DOMLook for <a> with /products/ hrefParent of product cardsElement 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-btn to prevent duplicates
  • Product URL query params (referral tracking like ?pr_prod_strat=...) are stripped — use the clean canonical URL for du
  • 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.