Shopify themes, liquid, logos, and UX
Hey, I need to add a "Load more" button below the collection page. I am using dawn theme. Can anyone help?
Thanks 🙂
Solved! Go to the solution
This is an accepted solution.
- Here is the solution for you @leechoostore
- Please follow these steps:
- Find the asset folder and add a new file "Ajaxinate.min.js" with the following content:
'use strict';
var Ajaxinate = function ajaxinateConstructor(config) {
var settings = config || {};
/*
pagination: Selector of pagination container
method: [options are 'scroll', 'click']
container: Selector of repeating content
offset: 0, offset the number of pixels before the bottom to start loading more on scroll
loadingText: 'Loading', The text changed during loading
callback: null, function to callback after a new page is loaded
*/
var defaultSettings = {
pagination: '#AjaxinatePagination',
method: 'scroll',
container: '#AjaxinateLoop',
offset: 0,
loadingText: 'Loading',
callback: null
};
// Merge configs
this.settings = Object.assign(defaultSettings, settings);
// Bind 'this' to applicable prototype functions
this.addScrollListeners = this.addScrollListeners.bind(this);
this.addClickListener = this.addClickListener.bind(this);
this.checkIfPaginationInView = this.checkIfPaginationInView.bind(this);
this.stopMultipleClicks = this.stopMultipleClicks.bind(this);
this.destroy = this.destroy.bind(this);
// Set up our element selectors
this.containerElement = document.querySelector(this.settings.container);
this.paginationElement = document.querySelector(this.settings.pagination);
this.initialize();
};
Ajaxinate.prototype.initialize = function initializeTheCorrectFunctionsBasedOnTheMethod() {
// Find and initialise the correct function based on the method set in the config
if (this.containerElement) {
var initializers = {
click: this.addClickListener,
scroll: this.addScrollListeners
};
initializers[this.settings.method]();
}
};
Ajaxinate.prototype.addScrollListeners = function addEventListenersForScrolling() {
if (this.paginationElement) {
document.addEventListener('scroll', this.checkIfPaginationInView);
window.addEventListener('resize', this.checkIfPaginationInView);
window.addEventListener('orientationchange', this.checkIfPaginationInView);
}
};
Ajaxinate.prototype.addClickListener = function addEventListenerForClicking() {
if (this.paginationElement) {
this.nextPageLinkElement = this.paginationElement.querySelector('a');
this.clickActive = true;
if (this.nextPageLinkElement !== null) {
this.nextPageLinkElement.addEventListener('click', this.stopMultipleClicks);
}
}
};
Ajaxinate.prototype.stopMultipleClicks = function handleClickEvent(event) {
event.preventDefault();
if (this.clickActive) {
this.nextPageLinkElement.innerHTML = this.settings.loadingText;
this.nextPageUrl = this.nextPageLinkElement.href;
this.clickActive = false;
this.loadMore();
}
};
Ajaxinate.prototype.checkIfPaginationInView = function handleScrollEvent() {
var top = this.paginationElement.getBoundingClientRect().top - this.settings.offset;
var bottom = this.paginationElement.getBoundingClientRect().bottom + this.settings.offset;
if (top <= window.innerHeight && bottom >= 0) {
this.nextPageLinkElement = this.paginationElement.querySelector('a');
this.removeScrollListener();
if (this.nextPageLinkElement) {
this.nextPageLinkElement.innerHTML = this.settings.loadingText;
this.nextPageUrl = this.nextPageLinkElement.href;
this.loadMore();
}
}
};
Ajaxinate.prototype.loadMore = function getTheHtmlOfTheNextPageWithAnAjaxRequest() {
this.request = new XMLHttpRequest();
this.request.onreadystatechange = function success() {
if (this.request.readyState === 4 && this.request.status === 200) {
var newContainer = this.request.responseXML.querySelectorAll(this.settings.container)[0];
var newPagination = this.request.responseXML.querySelectorAll(this.settings.pagination)[0];
this.containerElement.insertAdjacentHTML('beforeend', newContainer.innerHTML);
this.paginationElement.innerHTML = newPagination.innerHTML;
if (this.settings.callback && typeof this.settings.callback === 'function') {
this.settings.callback(this.request.responseXML);
}
this.initialize();
}
}.bind(this);
this.request.open('GET', this.nextPageUrl);
this.request.responseType = 'document';
this.request.send();
};
Ajaxinate.prototype.removeClickListener = function removeClickEventListener() {
this.nextPageLinkElement.addEventListener('click', this.stopMultipleClicks);
};
Ajaxinate.prototype.removeScrollListener = function removeScrollEventListener() {
document.removeEventListener('scroll', this.checkIfPaginationInView);
window.removeEventListener('resize', this.checkIfPaginationInView);
window.removeEventListener('orientationchange', this.checkIfPaginationInView);
};
Ajaxinate.prototype.destroy = function removeEventListenersAndReturnThis() {
// This method is used to unbind event listeners from the DOM
// This function is called manually to destroy "this" Ajaxinate instance
var destroyers = {
click: this.removeClickListener,
scroll: this.removeScrollListener
};
destroyers[this.settings.method]();
return this;
};
- Then add the following code to the main-collection-product-grid.liquid
{{ 'ajaxinate.min.js' | asset_url | script_tag }}
- Add the following code before {% schema %} in main-collection-product-grid.liquid
<script>
const endlessCollection = new Ajaxinate({
container: '#product-grid',
pagination: '.infinite_next',
});
</script>
- Find the file pagination.liquid:
- Search for {%- if paginate.next -%} and add class="infinite_next" in <li>. So the code should look like below:
{%- if paginate.next -%}
<li class="infinite_next">
<a
href="{{ paginate.next.url }}{{ anchor }}"
class="pagination__item pagination__item--prev pagination__item-arrow link motion-reduce"
aria-label="{{ 'general.pagination.next' | t }}"
>
{%- render 'icon-caret' -%}
</a>
</li>
{%- endif -%}
- Adding code in facet.js File
- Search for static renderProductCount(html) and below that add the following code:
const endlessCollection = new Ajaxinate({
container: '#product-grid',
pagination: '.infinite_next',
});
- Adding code in component-pagination.css File
.pagination__list{
display: none;
}
- Please press 'Like' and mark it as 'Solution' if you find it helpful. Thank you.
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
The above code will help you automatically load more products on the collection page when customers scroll to the bottom of the page
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
@leechoostore if you want the "load more" button. follow the step
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
- Add this code to theme.liquid
{% if template contains 'collection' %}
<script src="{{ 'collection-load-more.js' | asset_url }}" defer="defer"></script>
{%endif%}
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
Add file collection-load-more.js in assest with content
const products_on_page = document.getElementById('product-grid');
const nextUrl = document.getElementById('paginateNext');
let next_url = nextUrl.dataset.nextUrl;
const load_more_btn = document.getElementsByClassName('load-more_btn')[0];
const load_more_spinner = document.getElementsByClassName('load-more_spinner')[0];
async function getNextPage() {
try {
let res = await fetch(next_url);
return await res.text();
} catch (error) {
console.log(error);
}
}
async function loadMoreProducts() {
load_more_btn.style.display = 'none';
load_more_spinner.style.display = 'block';
let nextPage = await getNextPage();
const parser = new DOMParser();
const nextPageDoc = parser.parseFromString(nextPage, 'text/html');
load_more_spinner.style.display = 'none';
const productgrid = nextPageDoc.getElementById('product-grid');
const new_products = productgrid.getElementsByClassName('grid__item');
const newUrl = document.getElementById('paginateNext');
const new_url = newUrl.dataset.nextUrl;
if (new_url) {
load_more_btn.style.display = 'flex';
}
next_url = new_url;
for (let i = 0; i < new_products.length; i++) {
products_on_page.appendChild(new_products[i]);
}
}
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
- In the file main-collection-product-grid find {%- if paginate.pages > 1 -%}
and replace
{%- if paginate.pages > 1 -%}
{% render 'pagination', paginate: paginate, anchor: '' %}
{%- endif -%}
to
{%- if paginate.pages > 1 -%}
<div class="load-more">
<a class="load-more_btn text-center mt-12 button button--primary" onclick="loadMoreProducts()">Load More</a>
<div class="load-more_spinner hidden w-8 h-8 ml-auto mr-auto border-4 border-solid border-black border-t-gray-200 rounded-full animate-spin"></div>
</div>
{%- endif -%}
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
- Here is the result you will achieve:
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
Please try to follow my instructions @leechoostore
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
This worked perfectly for infinite scroll! Thank you so much!
This is an accepted solution.
This process worked perfectly for "Load more" button! Thanks a bunch!
This is an accepted solution.
- Here is the solution for you @leechoostore
- Please follow these steps:
- Find the asset folder and add a new file "Ajaxinate.min.js" with the following content:
'use strict';
var Ajaxinate = function ajaxinateConstructor(config) {
var settings = config || {};
/*
pagination: Selector of pagination container
method: [options are 'scroll', 'click']
container: Selector of repeating content
offset: 0, offset the number of pixels before the bottom to start loading more on scroll
loadingText: 'Loading', The text changed during loading
callback: null, function to callback after a new page is loaded
*/
var defaultSettings = {
pagination: '#AjaxinatePagination',
method: 'scroll',
container: '#AjaxinateLoop',
offset: 0,
loadingText: 'Loading',
callback: null
};
// Merge configs
this.settings = Object.assign(defaultSettings, settings);
// Bind 'this' to applicable prototype functions
this.addScrollListeners = this.addScrollListeners.bind(this);
this.addClickListener = this.addClickListener.bind(this);
this.checkIfPaginationInView = this.checkIfPaginationInView.bind(this);
this.stopMultipleClicks = this.stopMultipleClicks.bind(this);
this.destroy = this.destroy.bind(this);
// Set up our element selectors
this.containerElement = document.querySelector(this.settings.container);
this.paginationElement = document.querySelector(this.settings.pagination);
this.initialize();
};
Ajaxinate.prototype.initialize = function initializeTheCorrectFunctionsBasedOnTheMethod() {
// Find and initialise the correct function based on the method set in the config
if (this.containerElement) {
var initializers = {
click: this.addClickListener,
scroll: this.addScrollListeners
};
initializers[this.settings.method]();
}
};
Ajaxinate.prototype.addScrollListeners = function addEventListenersForScrolling() {
if (this.paginationElement) {
document.addEventListener('scroll', this.checkIfPaginationInView);
window.addEventListener('resize', this.checkIfPaginationInView);
window.addEventListener('orientationchange', this.checkIfPaginationInView);
}
};
Ajaxinate.prototype.addClickListener = function addEventListenerForClicking() {
if (this.paginationElement) {
this.nextPageLinkElement = this.paginationElement.querySelector('a');
this.clickActive = true;
if (this.nextPageLinkElement !== null) {
this.nextPageLinkElement.addEventListener('click', this.stopMultipleClicks);
}
}
};
Ajaxinate.prototype.stopMultipleClicks = function handleClickEvent(event) {
event.preventDefault();
if (this.clickActive) {
this.nextPageLinkElement.innerHTML = this.settings.loadingText;
this.nextPageUrl = this.nextPageLinkElement.href;
this.clickActive = false;
this.loadMore();
}
};
Ajaxinate.prototype.checkIfPaginationInView = function handleScrollEvent() {
var top = this.paginationElement.getBoundingClientRect().top - this.settings.offset;
var bottom = this.paginationElement.getBoundingClientRect().bottom + this.settings.offset;
if (top <= window.innerHeight && bottom >= 0) {
this.nextPageLinkElement = this.paginationElement.querySelector('a');
this.removeScrollListener();
if (this.nextPageLinkElement) {
this.nextPageLinkElement.innerHTML = this.settings.loadingText;
this.nextPageUrl = this.nextPageLinkElement.href;
this.loadMore();
}
}
};
Ajaxinate.prototype.loadMore = function getTheHtmlOfTheNextPageWithAnAjaxRequest() {
this.request = new XMLHttpRequest();
this.request.onreadystatechange = function success() {
if (this.request.readyState === 4 && this.request.status === 200) {
var newContainer = this.request.responseXML.querySelectorAll(this.settings.container)[0];
var newPagination = this.request.responseXML.querySelectorAll(this.settings.pagination)[0];
this.containerElement.insertAdjacentHTML('beforeend', newContainer.innerHTML);
this.paginationElement.innerHTML = newPagination.innerHTML;
if (this.settings.callback && typeof this.settings.callback === 'function') {
this.settings.callback(this.request.responseXML);
}
this.initialize();
}
}.bind(this);
this.request.open('GET', this.nextPageUrl);
this.request.responseType = 'document';
this.request.send();
};
Ajaxinate.prototype.removeClickListener = function removeClickEventListener() {
this.nextPageLinkElement.addEventListener('click', this.stopMultipleClicks);
};
Ajaxinate.prototype.removeScrollListener = function removeScrollEventListener() {
document.removeEventListener('scroll', this.checkIfPaginationInView);
window.removeEventListener('resize', this.checkIfPaginationInView);
window.removeEventListener('orientationchange', this.checkIfPaginationInView);
};
Ajaxinate.prototype.destroy = function removeEventListenersAndReturnThis() {
// This method is used to unbind event listeners from the DOM
// This function is called manually to destroy "this" Ajaxinate instance
var destroyers = {
click: this.removeClickListener,
scroll: this.removeScrollListener
};
destroyers[this.settings.method]();
return this;
};
- Then add the following code to the main-collection-product-grid.liquid
{{ 'ajaxinate.min.js' | asset_url | script_tag }}
- Add the following code before {% schema %} in main-collection-product-grid.liquid
<script>
const endlessCollection = new Ajaxinate({
container: '#product-grid',
pagination: '.infinite_next',
});
</script>
- Find the file pagination.liquid:
- Search for {%- if paginate.next -%} and add class="infinite_next" in <li>. So the code should look like below:
{%- if paginate.next -%}
<li class="infinite_next">
<a
href="{{ paginate.next.url }}{{ anchor }}"
class="pagination__item pagination__item--prev pagination__item-arrow link motion-reduce"
aria-label="{{ 'general.pagination.next' | t }}"
>
{%- render 'icon-caret' -%}
</a>
</li>
{%- endif -%}
- Adding code in facet.js File
- Search for static renderProductCount(html) and below that add the following code:
const endlessCollection = new Ajaxinate({
container: '#product-grid',
pagination: '.infinite_next',
});
- Adding code in component-pagination.css File
.pagination__list{
display: none;
}
- Please press 'Like' and mark it as 'Solution' if you find it helpful. Thank you.
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
The above code will help you automatically load more products on the collection page when customers scroll to the bottom of the page
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
This worked perfectly for infinite scroll! Thank you so much!
life saver, thank you!
This is an accepted solution.
@leechoostore if you want the "load more" button. follow the step
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
- Add this code to theme.liquid
{% if template contains 'collection' %}
<script src="{{ 'collection-load-more.js' | asset_url }}" defer="defer"></script>
{%endif%}
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
Add file collection-load-more.js in assest with content
const products_on_page = document.getElementById('product-grid');
const nextUrl = document.getElementById('paginateNext');
let next_url = nextUrl.dataset.nextUrl;
const load_more_btn = document.getElementsByClassName('load-more_btn')[0];
const load_more_spinner = document.getElementsByClassName('load-more_spinner')[0];
async function getNextPage() {
try {
let res = await fetch(next_url);
return await res.text();
} catch (error) {
console.log(error);
}
}
async function loadMoreProducts() {
load_more_btn.style.display = 'none';
load_more_spinner.style.display = 'block';
let nextPage = await getNextPage();
const parser = new DOMParser();
const nextPageDoc = parser.parseFromString(nextPage, 'text/html');
load_more_spinner.style.display = 'none';
const productgrid = nextPageDoc.getElementById('product-grid');
const new_products = productgrid.getElementsByClassName('grid__item');
const newUrl = document.getElementById('paginateNext');
const new_url = newUrl.dataset.nextUrl;
if (new_url) {
load_more_btn.style.display = 'flex';
}
next_url = new_url;
for (let i = 0; i < new_products.length; i++) {
products_on_page.appendChild(new_products[i]);
}
}
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
- In the file main-collection-product-grid find {%- if paginate.pages > 1 -%}
and replace
{%- if paginate.pages > 1 -%}
{% render 'pagination', paginate: paginate, anchor: '' %}
{%- endif -%}
to
{%- if paginate.pages > 1 -%}
<div class="load-more">
<a class="load-more_btn text-center mt-12 button button--primary" onclick="loadMoreProducts()">Load More</a>
<div class="load-more_spinner hidden w-8 h-8 ml-auto mr-auto border-4 border-solid border-black border-t-gray-200 rounded-full animate-spin"></div>
</div>
{%- endif -%}
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
- Add the code before div has id ProductGridContainer
<div id="paginateNext" data-next-url="{{paginate.next.url}}" style="display: none">{{paginate.next.url}}</div>
like image
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
Add this code to base.css
.load-more {
position: relative !important;
justify-content: center !important;
display: flex !important;
}
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
- Here is the result you will achieve:
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is an accepted solution.
Please try to follow my instructions @leechoostore
If our suggestions are useful, please let us know by giving it a like or marking it as a solution.
Salepify: Efficiently increase sales conversion with sale-driven features like auto add to cart, free gifts (free plan available)
Salemate: Boost your AVO with 2-layer offer, countdown upsell in your post purchase page
This is great - really helpful, thank you.
I'm having a small issue which is the button is only loading half the number of items that the pagination is set to. I'm not a complete novice when it comes to javascript but I couldn't see anything in the code that would be causing that, and when I revert to the shopify built-in pagination with pages, the correct number of products loads per page.
Any ideas what could be causing this? I haven't edited your JS file, and my product grid liquid is below:
{{ 'template-collection.css' | asset_url | stylesheet_tag }}
{{ 'component-card.css' | asset_url | stylesheet_tag }}
{{ 'component-price.css' | asset_url | stylesheet_tag }}
{% if section.settings.image_shape == 'blob' %}
{{ 'mask-blobs.css' | asset_url | stylesheet_tag }}
{%- endif -%}
{%- unless section.settings.quick_add == 'none' -%}
{{ 'quick-add.css' | asset_url | stylesheet_tag }}
{%- endunless -%}
{{ 'custom__card.css' | asset_url | stylesheet_tag }}
{{ 'custom__collection.css' | asset_url | stylesheet_tag }}
{%- if section.settings.quick_add == 'standard' -%}
<script src="{{ 'quick-add.js' | asset_url }}" defer="defer"></script>
<script src="{{ 'product-form.js' | asset_url }}" defer="defer"></script>
{%- endif -%}
{%- if section.settings.quick_add == 'bulk' -%}
<script src="{{ 'quick-add-bulk.js' | asset_url }}" defer="defer"></script>
<script src="{{ 'quantity-popover.js' | asset_url }}" defer="defer"></script>
<script src="{{ 'price-per-item.js' | asset_url }}" defer="defer"></script>
<script src="{{ 'quick-order-list.js' | asset_url }}" defer="defer"></script>
{%- endif -%}
<script src="{{ 'product-grid-layout.js' | asset_url }}" defer="defer"></script>
<script src="{{ 'collection-load-more.js' | asset_url }}" defer="defer"></script>
{%- style -%}
.section-{{ section.id }}-padding {
padding-top: {{ section.settings.padding_top | times: 0.75 | round: 0 }}px;
padding-bottom: {{ section.settings.padding_bottom | times: 0.75 | round: 0 }}px;
}
@media screen and (min-width: 750px) {
.section-{{ section.id }}-padding {
padding-top: {{ section.settings.padding_top }}px;
padding-bottom: {{ section.settings.padding_bottom }}px;
}
}
{%- endstyle -%}
<div class="section-{{ section.id }}-padding gradient color-{{ section.settings.color_scheme }}">
{%- paginate collection.products by section.settings.products_per_page -%}
{% comment %} Sort is the first tabbable element when filter type is vertical {% endcomment %}
{%- if section.settings.enable_sorting and section.settings.filter_type == 'vertical' -%}
<facet-filters-form class="facets facets-vertical-sort page-width small-hide">
<form class="facets-vertical-form" id="FacetSortForm">
<div class="facet-filters sorting caption">
<div class="facet-filters__field">
<h2 class="facet-filters__label caption-large text-body">
<label for="SortBy">{{ 'products.facets.sort_by_label' | t }}</label>
</h2>
<div class="select">
{%- assign sort_by = collection.sort_by | default: collection.default_sort_by -%}
<select
name="sort_by"
class="facet-filters__sort select__select caption-large"
id="SortBy"
aria-describedby="a11y-refresh-page-message"
>
{%- for option in collection.sort_options -%}
<option
value="{{ option.value | escape }}"
{% if option.value == sort_by %}
selected="selected"
{% endif %}
>
{{ option.name | escape }}
</option>
{%- endfor -%}
</select>
<span class="svg-wrapper">
{{- 'icon-caret.svg' | inline_asset_content -}}
</span>
</div>
</div>
</div>
</form>
</facet-filters-form>
{%- endif -%}
<div class="{% if section.settings.filter_type == 'vertical' %} facets-vertical page-width{% endif %}">
{{ 'component-facets.css' | asset_url | stylesheet_tag }}
<script src="{{ 'facets.js' | asset_url }}" defer="defer"></script>
{%- if section.settings.enable_filtering or section.settings.enable_sorting -%}
<aside
aria-labelledby="verticalTitle"
class="facets-wrapper{% unless section.settings.enable_filtering %} facets-wrapper--no-filters{% endunless %}{% if section.settings.filter_type != 'vertical' %} page-width{% endif %}"
id="main-collection-filters"
data-id="{{ section.id }}"
>
<div class="collection-title-wrapper">
<h2 class="collection-title h3">{{ collection.title }}</h2>
</div>
<div class="facet-description-wrapper">
{% if section.settings.show_description %}
<button
class="facet-description body-font button-label small-hide"
onclick="showDescription()"
role="button"
>
Description
</button>
{% endif %}
{% render 'facets',
results: collection,
enable_filtering: section.settings.enable_filtering,
enable_sorting: section.settings.enable_sorting,
filter_type: section.settings.filter_type,
paginate: paginate
%}
</div>
</aside>
{% if section.settings.show_description %}
<div id="collectionDescription" class="collection-description page-width">
{{ collection.description }}
</div>
{% endif %}
{%- endif -%}
<div id="paginateNext" data-next-url="{{paginate.next.url}}" style="display: none">{{ paginate.next.url }}</div>
<div
class="product-grid-container{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--slide-in{% endif %}"
id="ProductGridContainer"
{% if settings.animations_reveal_on_scroll %}
data-cascade
{% endif %}
>
{%- if collection.products.size == 0 -%}
<div
class="collection collection--empty {% if section.settings.margins %}page-width {% endif %}"
id="product-grid"
data-id="{{ section.id }}"
>
<div class="loading-overlay gradient"></div>
<div class="title-wrapper center">
<h2 class="title title--primary">
{{ 'sections.collection_template.empty' | t -}}
<br>
{{
'sections.collection_template.use_fewer_filters_html'
| t: link: collection.url, class: 'underlined-link link'
}}
</h2>
</div>
</div>
{%- else -%}
<div
class="collection{% if section.settings.filter_type != 'vertical' and section.settings.margins %} page-width{% endif %}"
>
<div class="loading-overlay gradient"></div>
<div
id="product-grid"
data-id="{{ section.id }}"
class="
grid product-grid main-product-grid grid--{{ section.settings.columns_mobile }}-col-tablet-down grid--{{ section.settings.columns_desktop }}-col-desktop
{% if section.settings.quick_add == 'bulk' %} collection-quick-add-bulk{% endif %}
"
>
{% assign skip_card_product_styles = false %}
{%- for product in collection.products -%}
{% assign page_number = paginate.current_page | minus: 1 %}
{% assign base_index = section.settings.products_per_page | times: page_number %}
{% assign grid_index = base_index | plus: forloop.index %}
{% assign lazy_load = false %}
{%- if grid_index > 4 -%}
{%- assign lazy_load = true -%}
{%- endif -%}
{% for block in section.blocks %}
{% case block.type %}
{% when 'content' %}
{% if block.settings.index == grid_index %}
{% render 'grid-content-item',
span_horizontal: block.settings.horizontal_span,
span_vertical: block.settings.vertical_span,
forloop: forloop,
image: block.settings.image,
title: block.settings.title,
link: block.settings.link,
link_label: block.settings.link_label,
color_scheme: block.settings.color_scheme,
block_id: block.id
%}
{% endif %}
{% endcase %}
{% endfor %}
{% render 'grid-card-product',
forloop: forloop,
card_product: product,
media_aspect_ratio: section.settings.image_ratio,
image_shape: section.settings.image_shape,
show_secondary_image: section.settings.show_secondary_image,
show_vendor: section.settings.show_vendor,
show_rating: section.settings.show_rating,
lazy_load: lazy_load,
skip_styles: skip_card_product_styles,
quick_add: section.settings.quick_add,
section_id: section.id
%}
{%- assign skip_card_product_styles = true -%}
{%- endfor -%}
</div>
{%- if paginate.pages > 1 -%}
<div class="load-more">
<a class="load-more_btn text-center mt-12 button button--primary" onclick="loadMoreProducts()"
>Load More</a
>
<div
class="load-more_spinner hidden w-8 h-8 ml-auto mr-auto border-4 border-solid border-black border-t-gray-200 rounded-full animate-spin"
></div>
</div>
{%- endif -%}
</div>
{%- endif -%}
</div>
</div>
{%- endpaginate -%}
{% if section.settings.image_shape == 'arch' %}
{{ 'mask-arch.svg' | inline_asset_content }}
{%- endif -%}
</div>
I have also the same issue with you.
I ended up using a different version of Ajaxinate that works for me.
"use strict";
var Ajaxinate = function ajaxinateConstructor(config) {
var settings = config || {};
/*
pagination: Selector of pagination container
method: [options are 'scroll', 'click']
container: Selector of repeating content
offset: 0, offset the number of pixels before the bottom to start loading more on scroll
loadingText: 'Loading', The text changed during loading
callback: null, function to callback after a new page is loaded
*/
var defaultSettings = {
pagination: "#AjaxinatePagination",
method: "scroll",
container: "#AjaxinateLoop",
offset: 0,
loadingText: "Loading",
callback: null,
};
// Merge configs
this.settings = Object.assign(defaultSettings, settings);
// Bind 'this' to applicable prototype functions
this.addScrollListeners = this.addScrollListeners.bind(this);
this.addClickListener = this.addClickListener.bind(this);
this.checkIfPaginationInView = this.checkIfPaginationInView.bind(this);
this.stopMultipleClicks = this.stopMultipleClicks.bind(this);
this.destroy = this.destroy.bind(this);
// Set up our element selectors
this.containerElement = document.querySelector(this.settings.container);
this.paginationElement = document.querySelector(this.settings.pagination);
this.initialize();
};
Ajaxinate.prototype.initialize =
function initializeTheCorrectFunctionsBasedOnTheMethod() {
// Find and initialise the correct function based on the method set in the config
if (this.containerElement) {
var initializers = {
click: this.addClickListener,
scroll: this.addScrollListeners,
};
initializers[this.settings.method]();
}
};
Ajaxinate.prototype.addScrollListeners =
function addEventListenersForScrolling() {
if (this.paginationElement) {
document.addEventListener("scroll", this.checkIfPaginationInView);
window.addEventListener("resize", this.checkIfPaginationInView);
window.addEventListener(
"orientationchange",
this.checkIfPaginationInView
);
}
};
Ajaxinate.prototype.addClickListener = function addEventListenerForClicking() {
if (this.paginationElement) {
this.nextPageLinkElement = this.paginationElement.querySelector("a");
this.clickActive = true;
if (this.nextPageLinkElement !== null) {
this.nextPageLinkElement.addEventListener(
"click",
this.stopMultipleClicks
);
}
}
};
Ajaxinate.prototype.stopMultipleClicks = function handleClickEvent(event) {
event.preventDefault();
if (this.clickActive) {
this.nextPageLinkElement.innerHTML = this.settings.loadingText;
this.nextPageUrl = this.nextPageLinkElement.href;
this.clickActive = false;
this.loadMore();
}
};
Ajaxinate.prototype.checkIfPaginationInView = function handleScrollEvent() {
var top =
this.paginationElement.getBoundingClientRect().top - this.settings.offset;
var bottom =
this.paginationElement.getBoundingClientRect().bottom +
this.settings.offset;
if (top <= window.innerHeight && bottom >= 0) {
this.nextPageLinkElement = this.paginationElement.querySelector("a");
this.removeScrollListener();
if (this.nextPageLinkElement) {
this.nextPageLinkElement.innerHTML = this.settings.loadingText;
this.nextPageUrl = this.nextPageLinkElement.href;
this.loadMore();
}
}
};
Ajaxinate.prototype.loadMore = function loadMore() {
this.request = new XMLHttpRequest();
this.request.onreadystatechange = function success() {
if (!this.request.responseXML) {
return;
}
if (!this.request.readyState === 4 || !this.request.status === 200) {
return;
}
var newContainer = this.request.responseXML.querySelectorAll(
this.settings.container
)[0];
var newPagination = this.request.responseXML.querySelectorAll(
this.settings.pagination
)[0];
this.containerElement.insertAdjacentHTML(
"beforeend",
newContainer.innerHTML
);
if (typeof newPagination === "undefined") {
this.removePaginationElement();
} else {
this.paginationElement.innerHTML = newPagination.innerHTML;
if (
this.settings.callback &&
typeof this.settings.callback === "function"
) {
this.settings.callback(this.request.responseXML);
}
this.initialize();
}
}.bind(this);
this.request.open("GET", this.nextPageUrl);
this.request.responseType = "document";
this.request.send();
};
Ajaxinate.prototype.removeClickListener = function removeClickEventListener() {
this.nextPageLinkElement.addEventListener("click", this.stopMultipleClicks);
};
Ajaxinate.prototype.removePaginationElement =
function removePaginationElement() {
this.paginationElement.innerHTML = "";
this.destroy();
};
Ajaxinate.prototype.removeScrollListener =
function removeScrollEventListener() {
document.removeEventListener("scroll", this.checkIfPaginationInView);
window.removeEventListener("resize", this.checkIfPaginationInView);
window.removeEventListener(
"orientationchange",
this.checkIfPaginationInView
);
};
Ajaxinate.prototype.destroy = function removeEventListenersAndReturnThis() {
// This method is used to unbind event listeners from the DOM
// This function is called manually to destroy "this" Ajaxinate instance
var destroyers = {
click: this.removeClickListener,
scroll: this.removeScrollListener,
};
destroyers[this.settings.method]();
return this;
};
It's also necessary to updates facets.js (assuming you're on Dawn or another base Shopify template) to refire the ajaxinate when facets are applied or disapplied. This is done by replacing this function in facets.js:
static renderProductGridContainer(html) {
document.getElementById('ProductGridContainer').innerHTML = new DOMParser()
.parseFromString(html, 'text/html')
.getElementById('ProductGridContainer').innerHTML;
document
.getElementById('ProductGridContainer')
.querySelectorAll('.scroll-trigger')
.forEach((element) => {
element.classList.add('scroll-trigger--cancel');
});
}
with this:
static renderProductGridContainer(html) {
document.getElementById("ProductGridContainer").innerHTML = new DOMParser()
.parseFromString(html, "text/html")
.getElementById("ProductGridContainer").innerHTML;
document
.getElementById("ProductGridContainer")
.querySelectorAll(".scroll-trigger")
.forEach((element) => {
element.classList.add("scroll-trigger--cancel");
});
const endlessCollection = new Ajaxinate({
container: "#product-grid",
pagination: "#AjaxinatePagination",
method: "click",
});
}
Obviously adjust the settings here as needed according to the Ajaxinate settings you want to use.
Then on my main collection page I have this for the button:
{%- if paginate.next -%}
<div id="AjaxinatePagination">
<a href="{{ paginate.next.url }}" class="button button--primary paginate-button">Load More</a>
</div>
{%- endif -%}
and this script at the bottom of the section:
<script>
document.addEventListener("DOMContentLoaded", function() {
const endlessCollection = new Ajaxinate({
container: '#product-grid',
pagination: '#AjaxinatePagination',
method: "click"
});
});
</script>
(Again obviously adjust settings as needed). Hope that helps!
This is an accepted solution.
This process worked perfectly for "Load more" button! Thanks a bunch!
2m ago Learn the essential skills to navigate the Shopify admin with confidence. T...
By Shopify Feb 12, 2025Learn how to expand your operations internationally with Shopify Academy’s learning path...
By Shopify Feb 4, 2025Hey Community, happy February! Looking back to January, we kicked off the year with 8....
By JasonH Feb 3, 2025