Skip to content

Instantly share code, notes, and snippets.

@mherchel
Created September 7, 2022 13:14
Show Gist options
  • Save mherchel/39c2e97e67ef9a44942e6b9858561a0e to your computer and use it in GitHub Desktop.
Save mherchel/39c2e97e67ef9a44942e6b9858561a0e to your computer and use it in GitHub Desktop.
'use strict';
(function () {
/*
* Fetch more data like views_load_more.
*
* @todos
* Add Quicklink integration when https://github.com/GoogleChromeLabs/quicklink/issues/54 is resolved.
*/
Drupal.behaviors.load_more = {
'attach': function (context) {
const contentContainerSelector = drupalSettings.lullabotcom.loadMore.contentContainerSelector;
const contentContainer = context.querySelector(contentContainerSelector);
const pagerContainer = context.querySelector(drupalSettings.lullabotcom.loadMore.pagerSelector);
const currentURL = window.location.protocol + '//' + window.location.host + window.location.pathname;
function handleClick() {
const pageToFetch = getPageNumberFromURL() + 1;
pagerContainer.querySelector('.load-more').classList.add('is-loading');
window.location.hash = 'page' + pageToFetch;
fetchData(pageToFetch)
.then(data => appendDatatoView(data));
}
// Looks at the hash and returns the current page.
function getPageNumberFromURL() {
return parseInt(location.hash ? location.hash.replace('#page', '') : 1);
}
/**
* Load all previous pages including the current page (as indicated by the hash) into the DOM.
*/
function loadPreviousPages() {
const currentPageNumber = getPageNumberFromURL();
const pagePromises = [];
const pagesData = {};
// If we're on the initial page (no hash exists), exit.
if (currentPageNumber === 1) return;
// If the data is already loaded, abort.
if (!contentContainer || contentContainer.classList.contains('is-loaded')) return;
pagerContainer.querySelector('.load-more').classList.add('is-loading');
// Loop through pages starting on page 2 through the current page, and fetch data.
for (let i = 2; i <= currentPageNumber; i++) {
// Add all promises to array, so we can call Promises.all on it later.
pagePromises.push(
fetchData(i)
.then(pageData => {
// Create an object containing arrays of elements (one-indexed).
pagesData[i - 1] = pageData;
})
);
}
// When all fetches have completed, combine into one giant array, and pass to appendDatatoView().
Promise.all(pagePromises).then(() => {
let combinedPagesArr = [];
for (let i = 1; i <= Object.keys(pagesData).length; i++) {
combinedPagesArr.push(...pagesData[i]);
}
appendDatatoView(combinedPagesArr);
// CSS class to indicate the data is loaded.
contentContainer.classList.add('is-loaded');
});
}
/**
* Fetch data in the specified page.
* @param pageToFetch Number of the page to fetch
* @returns { Promise } Promise object which returns an array of elements.
* */
function fetchData(pageToFetch) {
// Get page's querystring and update the 'page' parameter.
const urlParams = new URLSearchParams(window.location.search);
urlParams.set('page', pageToFetch - 1); // Views pages are zero indexed.
const fetchURL = currentURL + '?' + urlParams.toString();
return fetch(fetchURL).then(response => {
return response.text();
}).then(htmlText => {
const parser = new DOMParser;
const pageNodeList = parser.parseFromString(htmlText, 'text/html').querySelectorAll(contentContainerSelector + ' > *');
// Convert nodelist to array.
const pageDataArray = Array.from(pageNodeList);
// Create and prepend page number anchor to pageDataArray if there is data.
if (pageDataArray.length) {
const pageAnchor = parser.parseFromString(`<a href="#page${pageToFetch}" class="visually-hidden page-anchor">Page ${pageToFetch}</a>`, 'text/html').querySelector('a');
pageDataArray.unshift(pageAnchor);
}
return pageDataArray;
});
}
/**
* Append data to the DOM.
* @param data Array of elements
*/
function appendDatatoView(data) {
// If no more data is to be had, hide the button.
if (!data.length) {
pagerContainer.querySelector('.load-more').classList.add('u-hidden');
return;
}
const contentHtml = document.createDocumentFragment();
data.forEach((currentValue) => {
contentHtml.append(currentValue);
});
contentContainer.append(contentHtml);
// Focus the last page anchor for a11y.
const pageAnchors = contentContainer.querySelectorAll('a.page-anchor');
pageAnchors[pageAnchors.length - 1].focus();
pagerContainer.querySelector('.load-more').classList.remove('is-loading');
Drupal.announce('Additional data has been loaded.');
}
loadPreviousPages();
if (pagerContainer) {
pagerContainer.querySelector('.load-more__button').addEventListener('click', handleClick);
}
},
};
}) ();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment