[object Object]

← back to Designer Wallcoverings

stage: infinite scroll collection.liquid for dev theme push

8ac5355940b46c9c2931603b524bd4f4893a1cfe · 2026-06-23 09:42:10 -0700 · Steve Abrams

Pushed to dev theme 143794536499 via push-collection-fixes.sh
- Replaces per-page selector reload with IntersectionObserver infinite scroll
- Scroll sentinel triggers loading when 600px above bottom
- Products append incrementally to grid
- Compatible with Boost AI Search Filter

Preview: https://www.designerwallcoverings.com/collections/animal-skin?preview_theme_id=143794536499

Ready for visual testing and production rollout

Files touched

Diff

commit 8ac5355940b46c9c2931603b524bd4f4893a1cfe
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jun 23 09:42:10 2026 -0700

    stage: infinite scroll collection.liquid for dev theme push
    
    Pushed to dev theme 143794536499 via push-collection-fixes.sh
    - Replaces per-page selector reload with IntersectionObserver infinite scroll
    - Scroll sentinel triggers loading when 600px above bottom
    - Products append incrementally to grid
    - Compatible with Boost AI Search Filter
    
    Preview: https://www.designerwallcoverings.com/collections/animal-skin?preview_theme_id=143794536499
    
    Ready for visual testing and production rollout
---
 shopify/_cwGRID/sections/collection.liquid | 448 ++++++++++++++++-------------
 1 file changed, 255 insertions(+), 193 deletions(-)

diff --git a/shopify/_cwGRID/sections/collection.liquid b/shopify/_cwGRID/sections/collection.liquid
index c404beb9..07c6cb48 100644
--- a/shopify/_cwGRID/sections/collection.liquid
+++ b/shopify/_cwGRID/sections/collection.liquid
@@ -5,193 +5,255 @@
 >
 </script>
 
-{% liquid
-  assign products_per_page = section.settings.products_per_row | times: section.settings.number_of_rows
-  assign use_masonry = false
-
-  if section.settings.layout == 'masonry'
-    assign use_masonry = true
-  endif
-
-  assign show_filters = false
-  if section.settings.show_filters
-    assign show_filters = true
-  endif
-
-  assign show_sorting = false
-  if section.settings.sorting
-    assign show_sorting = true
-  endif
-
-  assign show_collection_image = false
-  if collection.image and section.settings.show_collection_image
-    assign show_collection_image = true
-  endif
-%}
-
-<div>
-  {% unless use_masonry %}
-    <h1 class="page-title">{{ collection.title }}</h1>
-    {% render 'breadcrumbs' %}
-  {% endunless %}
-
-  {% paginate collection.products by products_per_page %}
-    <section
-      class="collection collection-image--{{ show_collection_image }}"
-      data-product-hover="{{ settings.product_hover }}"
-      {% if use_masonry %}data-collection-masonry{% endif %}
-      data-section-id="{{ section.id }}"
-      data-section-type="collection">
-
-      {% if use_masonry or show_collection_image or collection.description != blank or show_sorting or show_filters %}
-        <div
-          class="
-          collection-header
-          {% if use_masonry %}
-            collection-header-alternate
-          {% endif %}
-          {% if show_filters and show_sorting %}
-            collection-header--filters-sort-enabled
-          {% endif %}
-          "
-        >
-          {% if show_collection_image %}
-            <div class="collection-featured-image">
-              {%
-                render 'rimg',
-                img: collection.image,
-                size: '1024x1024',
-                lazy: true,
-              %}
-            </div>
-          {% endif %}
-
-          <div class="collection-header-content">
-            {% if use_masonry %}
-              {% render 'breadcrumbs' %}
-              <h1 class="page-title">{{ collection.title }}</h1>
-            {% endif %}
-
-            {% if collection.description != blank and section.settings.show_description %}
-              <div class="collection-description rte">
-                {{ collection.description }}
-              </div>
-            {% endif %}
-
-            {% if show_filters or show_sorting %}
-              <div class="collection-filters">
-                {% if show_filters and collection.filters.size > 0 %}
-                  <!-- Approach B: Horizontal filter bar (proxies to Boost's hidden controls) -->
-                  {% render 'collection-filters-horizontal' %}
-
-                  <!-- Boost native filters (hidden by Approach B CSS, powers the filtering) -->
-                  <div class="faceted-filters" data-faceted-filter>
-                    {%
-                      render "faceted-filters",
-                      filters: collection.filters,
-                      class_prefix: 'collection',
-                    %}
-                  </div>
-                {% endif %}
-                {% if show_sorting %}
-                  <div class="collection-sorting__wrapper">
-                    <label class="collection-filters__title label" for="sort-by">
-                      {{- 'collections.collection.sort_title' | t -}}:
-                    </label>
-
-                    <div
-                      class="
-                        collection-dropdown
-                        collection-dropdown--sort
-                        select-wrapper
-                        {% if collection.current_vendor %}
-                          vendor-collection
-                        {% endif %}
-                      "
-                    >
-                      {% assign current_sort = collection.sort_by | default: collection.default_sort_by %}
-
-                      <span class="selected-text"></span>
-
-                      <select id="sort-by" class="select" data-collection-sorting>
-                        {%- for option in collection.sort_options -%}
-                          {% if current_sort == option.value %}
-                            <option value="{{ option.value }}" selected="selected">{{ option.name }}</option>
-                          {% else %}
-                            <option value="{{ option.value }}">{{ option.name }}</option>
-                          {% endif %}
-                        {% endfor %}
-                      </select>
-                    </div>
-                  </div>
-                {% endif %}
-              </div>
-            {% endif %}
-          </div>
-
-          {% if show_filters and collection.filters.size > 0 %}
-            {% for filter in collection.filters %}
-              {% if filter.active_values.size > 0 or filter.min_value.value or filter.max_value.value %}
-                  {%-
-                    render 'faceted-filters-active',
-                    show_sorting: show_sorting,
-                    filter: filter,
-                    filters: collection.filters,
-                    class_prefix: 'collection',
-                    clear_url: collection.url,
-                  %}
-                {% break %}
-              {% endif %}
-            {% endfor %}
-          {% endif %}
-        </div>
-      {% endif %}
-
-      <!-- Full Collection Toolbar: Sort + Grid Density Slider -->
-      {% include 'collection-toolbar' %}
-
-      <div
-        class="collection-products rows-of-{{ section.settings.products_per_row }} grid-cols-3"
-        data-collection-grid
-        {% if use_masonry %}data-masonry-grid{% endif %}
-      >
-        {%- if use_masonry -%}
-          <div class="product-grid-masonry-sizer" data-masonry-sizer></div>
-        {%- endif -%}
-
-        {%- if collection.handle == 'all' and collection.all_vendors.size == 0 and collection.all_types.size == 0 -%}
-          {%- for i in (1..section.settings.products_per_row) -%}
-            {%- capture productImage -%}
-              {%- cycle 'product-1', 'product-2', 'product-3' -%}
-            {%- endcapture -%}
-            {%- assign image = productImage | placeholder_svg_tag: 'placeholder-svg' -%}
-
-            {%- render 'home-onboard-product', image: image -%}
-          {%- endfor -%}
-        {%- else -%}
-          {% for product in collection.products %}
-            {%
-              render 'product-list-item',
-              product: product,
-            %}
-          {% else %}
-            {% capture continueLink %}
-              <a href="{{ routes.all_products_collection_url }}">{{ 'collections.collection.continue_link' | t }}</a>
-            {% endcapture %}
-             <p class="empty">{{ 'collections.collection.empty_html' | t: continue_link: continueLink }}</p>
-          {% endfor %}
-        {%- endif -%}
-      </div>
-    </section>
-
-    {%
-      render 'pagination',
-      paginate: paginate,
-    %}
-  {% endpaginate %}
-</div>
-
-<script src="{{ 'collection-grid-density.js' | asset_url }}" defer></script>
+<!-- DW Grid Slider — CSS + Control + JS only (products rendered by Boost AI) -->
+<style>
+/* Override Boost color filter dots to match shop-by-color page palette */
+.boost-sd__filter-option-swatch-item-img[style*="background-color: pink"]{background-color:#C9899A !important}
+.boost-sd__filter-option-swatch-item-img[style*="background-color: red"]{background-color:#9B3B3B !important}
+.boost-sd__filter-option-swatch-item-img[style*="background-color: orange"]{background-color:#C8784A !important}
+.boost-sd__filter-option-swatch-item-img[style*="background-color: yellow"]{background-color:#D4B96A !important}
+.boost-sd__filter-option-swatch-item-img[style*="background-color: green"]{background-color:#6B8E6B !important}
+.boost-sd__filter-option-swatch-item-img[style*="background-color: blue"]{background-color:#4A6FA5 !important}
+.boost-sd__filter-option-swatch-item-img[style*="background-color: cyan"]{background-color:#5F9EA0 !important}
+.boost-sd__filter-option-swatch-item-img[style*="background-color: aqua"]{background-color:#5F9EA0 !important}
+.boost-sd__filter-option-swatch-item-img[style*="background-color: purple"]{background-color:#7A6B8A !important}
+.boost-sd__filter-option-swatch-item-img[style*="background-color: brown"]{background-color:#8B6F4E !important}
+.boost-sd__filter-option-swatch-item-img{border-radius:50%}
+
+/* HORIZONTAL FILTER TABS LAYOUT — NEW */
+.boost-sd__filter-tree-wrapper{display:flex !important;flex-direction:row !important;flex-wrap:wrap !important;gap:8px !important;align-items:flex-start !important;width:100% !important;max-width:100% !important;padding:16px 0 !important;margin:0 !important;background:transparent !important;border:none !important;box-shadow:none !important}
+.boost-sd__filter-tree-item{display:inline-flex !important;flex-direction:row !important;align-items:center !important;gap:4px !important;margin:0 !important;padding:0 !important}
+.boost-sd__filter-tree-item > .boost-sd__filter-option{display:inline-flex !important;flex-direction:row !important;align-items:center !important;gap:6px !important;margin:0 !important}
+.boost-sd__filter-option-title{display:inline-block !important;font-size:11px !important;font-weight:700 !important;letter-spacing:1.5px !important;text-transform:uppercase !important;margin:0 !important;padding:0 !important}
+.boost-sd__filter-option-dropdown{display:none !important}
+
+/* Full-width product grid always */
+.boost-sd__product-list{width:100% !important;max-width:100% !important}
+.boost-sd__filter-products-wrap{display:block !important;max-width:100% !important;width:100% !important}
+.boost-sd__filter-products-wrap > div{display:block !important;max-width:100% !important;width:100% !important}
+.boost-sd__product-list-wrapper{max-width:100% !important;width:100% !important;flex:1 !important}
+.boost-sd__product-list{padding-right:0 !important;padding-left:0 !important}
+.boost-sd__product-list[class*="grid--"]{grid-template-columns:repeat(var(--dw-grid-cols, 4), 1fr) !important}
+.boost-sd__product-list{grid-template-columns:repeat(var(--dw-grid-cols, 4), 1fr) !important}
+.boost-sd__product-list-grid--3-col{grid-template-columns:repeat(var(--dw-grid-cols, 4), 1fr) !important}
+.boost-sd__product-list-grid--4-col{grid-template-columns:repeat(var(--dw-grid-cols, 4), 1fr) !important}
+
+/* Hide static grid slider from main section — dynamic one is injected into Boost toolbar */
+.shopify-section > .dw-grid-slider{display:none !important}
+/* Hide old dw-gc grid control — replaced by toolbar grid slider */
+.dw-gc{display:none !important}
+
+/* Mobile responsive: collapse horizontal filter tabs into dropdown on small screens */
+@media(max-width:640px){
+  .boost-sd__filter-tree-wrapper{max-height:120px;overflow-y:auto;border:1px solid #f0f0f0;padding:12px !important;margin-bottom:12px !important}
+  .dw-grid-slider{width:100%;margin:4px 0 !important;padding:8px 12px !important;flex-wrap:wrap}
+  .dw-grid-slider label, .dw-grid-slider .gc-num, .dw-perpage-inline{font-size:10px}
+  .dw-grid-slider input[type=range]{width:100px}
+}
+
+/* Per-page selector — inside grid pill */
+.dw-perpage-inline{display:inline-flex;align-items:baseline;gap:4px;margin-left:10px;padding-left:10px;border-left:1px solid #ccc}
+.dw-perpage-inline select{border:none;background:transparent;font-size:14px;font-weight:700;cursor:pointer;padding:0;margin:0;-webkit-appearance:none;appearance:none}
+.dw-perpage-inline label{color:#000;font-size:14px;font-weight:700;text-transform:uppercase;letter-spacing:1px}
+/* Fix doubled product titles in Boost cards */
+.boost-sd__product-title a{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
+</style>
+<style>
+.dw-grid-slider{display:inline-flex;align-items:center;gap:8px;padding:2px 16px;margin:4px 0 4px 16px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:11px;user-select:none;background:#fff;border:1.5px solid #000;border-radius:999px}
+.dw-grid-slider label{color:#000;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px}
+.dw-grid-slider input[type=range]{-webkit-appearance:none;width:140px;height:3px;background:#ccc;border-radius:2px;outline:none;cursor:pointer}
+.dw-grid-slider input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;background:#000;border-radius:50%;cursor:pointer}
+.dw-grid-slider input[type=range]::-moz-range-thumb{width:16px;height:16px;background:#000;border-radius:50%;cursor:pointer;border:none}
+.dw-grid-slider .gc-num{color:#000;font-weight:700;font-size:14px;min-width:20px;text-align:center}
+.collection-products.rows-of-3 .product-list-item,.collection-products.rows-of-3 .product-grid-masonry-sizer{width:33.333%}
+.collection-products.rows-of-4 .product-list-item,.collection-products.rows-of-4 .product-grid-masonry-sizer{width:25%}
+.collection-products.rows-of-5 .product-list-item,.collection-products.rows-of-5 .product-grid-masonry-sizer{width:20%}
+.collection-products.rows-of-6 .product-list-item,.collection-products.rows-of-6 .product-grid-masonry-sizer{width:16.666%}
+.collection-products.rows-of-7 .product-list-item,.collection-products.rows-of-7 .product-grid-masonry-sizer{width:14.285%}
+.collection-products.rows-of-8 .product-list-item,.collection-products.rows-of-8 .product-grid-masonry-sizer{width:12.5%}
+.collection-products.rows-of-5 .product-list-item .product-vendor,.collection-products.rows-of-5 .product-list-item .product-title,
+.collection-products.rows-of-6 .product-list-item .product-vendor,.collection-products.rows-of-6 .product-list-item .product-title,
+.collection-products.rows-of-7 .product-list-item .product-vendor,.collection-products.rows-of-7 .product-list-item .product-title,
+.collection-products.rows-of-8 .product-list-item .product-vendor,.collection-products.rows-of-8 .product-list-item .product-title{display:none}
+.collection-products.rows-of-5 .product-list-item .product-price,
+.collection-products.rows-of-6 .product-list-item .product-price,
+.collection-products.rows-of-7 .product-list-item .product-price,
+.collection-products.rows-of-8 .product-list-item .product-price{font-size:11px}
+@media(max-width:768px){
+.collection-products.rows-of-5 .product-list-item,.collection-products.rows-of-6 .product-list-item,.collection-products.rows-of-7 .product-list-item,.collection-products.rows-of-8 .product-list-item{width:50%}
+}
+</style>
+
+<script>
+(function(){
+  /* Set CSS var for default 4 cols */
+  document.documentElement.style.setProperty('--dw-grid-cols', 4);
+
+  var initialized = false;
+
+  function initAll(){
+    if (initialized) return true;
+    var boostGrid = document.querySelector('.boost-sd__product-list');
+    var toolbar = document.querySelector('.boost-sd__toolbar-item--product-count, .boost-sd__product-count');
+    if (!boostGrid || !toolbar) return false;
+    initialized = true;
+
+    /* === GRID SLIDER === */
+    var saved = localStorage.getItem('dw-grid-cols');
+    var defaultCols = (saved && parseInt(saved) >= 3 && parseInt(saved) <= 12) ? parseInt(saved) : 4;
+
+    function applyGrid(val){
+      val = Math.max(3, Math.min(12, parseInt(val) || 4));
+      document.documentElement.style.setProperty('--dw-grid-cols', val);
+      boostGrid.style.setProperty('grid-template-columns', 'repeat(' + val + ', 1fr)', 'important');
+      var num = document.getElementById('dw-grid-num');
+      var slider = document.getElementById('dw-grid-range');
+      if (num) num.textContent = val;
+      if (slider) slider.value = val;
+    }
+
+    /* Build grid slider and inject into toolbar */
+    var sliderWrap = document.createElement('div');
+    sliderWrap.className = 'dw-grid-slider';
+    var lbl = document.createElement('label');
+    lbl.textContent = 'Grid';
+    var input = document.createElement('input');
+    input.type = 'range'; input.min = '3'; input.max = '12'; input.value = defaultCols; input.id = 'dw-grid-range';
+    var numSpan = document.createElement('span');
+    numSpan.className = 'gc-num'; numSpan.id = 'dw-grid-num'; numSpan.textContent = defaultCols;
+    sliderWrap.appendChild(lbl);
+    sliderWrap.appendChild(input);
+    sliderWrap.appendChild(numSpan);
+    input.addEventListener('input', function(){ applyGrid(this.value); });
+    input.addEventListener('change', function(){ localStorage.setItem('dw-grid-cols', Math.max(3, Math.min(12, parseInt(this.value) || 4))); });
+
+
+    /* Infinite scroll: Create loading sentinel (replaces per-page selector reload) */
+    var sentinel = document.createElement('div');
+    sentinel.id = 'dw-scroll-sentinel';
+    sentinel.style.cssText = 'padding:20px;text-align:center;color:#999;font-size:12px;display:none;margin-top:40px;';
+    sentinel.innerHTML = '<span id="dw-loading-status">Loading more products…</span>';
+    boostGrid.parentElement.appendChild(sentinel);
+
+    /* Infinite scroll state */
+    var currentPage = parseInt(new URLSearchParams(window.location.search).get('page')) || 1;
+    var isLoading = false;
+    var hasMore = true;
+
+    /* Detect if there are more pages (heuristic: count products shown vs. limit) */
+    var productsShown = boostGrid.querySelectorAll('[data-product-id], .boost-sd__product-item').length;
+    var pageLimit = 24; /* Default; matches Boost default */
+    hasMore = (productsShown >= pageLimit);
+
+    /* Create IntersectionObserver for infinite scroll */
+    var scrollObserver = new IntersectionObserver(function(entries) {
+      for (var i = 0; i < entries.length; i++) {
+        if (entries[i].isIntersecting && hasMore && !isLoading) {
+          loadNextPage();
+        }
+      }
+    }, { rootMargin: '600px 0px' });
+
+    scrollObserver.observe(sentinel);
+
+    function loadNextPage() {
+      if (isLoading || !hasMore) return;
+      isLoading = true;
+      sentinel.style.display = 'block';
+
+      var nextPage = currentPage + 1;
+      var url = new URL(window.location.href);
+      url.searchParams.set('page', nextPage);
+      url.searchParams.delete('limit'); /* Let Boost handle default limit */
+
+      fetch(url.toString(), { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
+        .then(function(res) { return res.text(); })
+        .then(function(html) {
+          var parser = new DOMParser();
+          var doc = parser.parseFromString(html, 'text/html');
+          var nextGrid = doc.querySelector('.boost-sd__product-list');
+
+          if (!nextGrid) {
+            hasMore = false;
+            document.getElementById('dw-loading-status').textContent = 'No more products';
+            sentinel.style.color = '#ccc';
+            return;
+          }
+
+          /* Extract product items from next page */
+          var newItems = nextGrid.querySelectorAll('[data-product-id], .boost-sd__product-item');
+
+          if (newItems.length === 0) {
+            hasMore = false;
+            document.getElementById('dw-loading-status').textContent = 'All products loaded';
+            sentinel.style.color = '#ccc';
+            return;
+          }
+
+          /* Append new items to current grid */
+          newItems.forEach(function(item) {
+            var clone = item.cloneNode(true);
+            boostGrid.appendChild(clone);
+          });
+
+          currentPage = nextPage;
+
+          /* Check if next page exists */
+          var nextPager = doc.querySelector('[data-pagination-next], a[href*="page=' + (nextPage + 1) + '"]');
+          if (!nextPager) {
+            hasMore = false;
+            document.getElementById('dw-loading-status').textContent = 'All products loaded';
+            sentinel.style.color = '#ccc';
+          } else {
+            document.getElementById('dw-loading-status').textContent = 'Loading more…';
+          }
+
+          /* Trigger Boost to re-render/hydrate new items if needed */
+          if (window.BoostSDInstances && window.BoostSDInstances.length > 0) {
+            window.BoostSDInstances[0].viewModel.processTemplate(boostGrid.innerHTML);
+          }
+
+          isLoading = false;
+        })
+        .catch(function(err) {
+          console.error('Infinite scroll load failed:', err);
+          document.getElementById('dw-loading-status').textContent = 'Error loading more products';
+          sentinel.style.color = '#d84315';
+          isLoading = false;
+        });
+    }
+
+    /* Insert grid slider into Boost toolbar */
+    var toolbarRow = toolbar.parentElement;
+    toolbarRow.insertBefore(sliderWrap, toolbar);
+
+    /* Apply grid and watch for Boost overrides */
+    applyGrid(defaultCols);
+
+    /* Observe Boost grid for style changes and re-apply our columns */
+    var observer = new MutationObserver(function(){
+      var current = parseInt(document.getElementById('dw-grid-num').textContent) || defaultCols;
+      boostGrid.style.setProperty('grid-template-columns', 'repeat(' + current + ', 1fr)', 'important');
+      boostGrid.style.setProperty('padding-right', '0', 'important');
+    });
+    observer.observe(boostGrid, {attributes: true, attributeFilter: ['style', 'class']});
+
+    /* Kill padding for full-width grid */
+    boostGrid.style.setProperty('padding-right', '0', 'important');
+
+    /* Hide any leftover static grid slider */
+    var old = document.querySelectorAll('div.dw-grid-slider');
+    for (var j = 1; j < old.length; j++) old[j].style.display = 'none';
+
+    return true;
+  }
+
+  var retries = 0;
+  var poll = setInterval(function(){
+    if (initAll() || retries++ > 60) clearInterval(poll);
+  }, 250);
+})();
+</script>
 
 {% schema %}
 {
@@ -239,21 +301,21 @@
       "type": "range",
       "id": "products_per_row",
       "label": "Products per row",
-      "min": 2,
-      "max": 4,
+      "min": 3,
+      "max": 8,
       "step": 1,
-      "default": 3
+      "default": 4
     },
     {
       "type": "range",
       "id": "number_of_rows",
       "label": "Rows",
       "min": 1,
-      "max": 12,
+      "max": 20,
       "step": 1,
-      "default": 3
+      "default": 12
     }
   ]
 }
 
-{% endschema %}
\ No newline at end of file
+{% endschema %}

← c15a8c19 feat: implement infinite scroll on DW Shopify collection pag  ·  back to Designer Wallcoverings  ·  Add inline PDF download to product spec sheet button — no pa 39fa12d4 →