← 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
M shopify/_cwGRID/sections/collection.liquid
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 →