v7.2+ Infinite Scroll Implementation
This section describes the infinite scroll implementation in the project, designed to be reusable and extensible.
Main Components
infinite-scroll/infinite-scroll.js
:- This module handles the main logic of infinite scroll.
- It uses
IntersectionObserver
to detect when the user reaches the end of the page. - It inserts the new content into the page and updates the URL.
- It emits the
InfiniteScroll
event when new content is loaded, allowing other modules to react.
infinite-scroll/embed-scripts.js
:- Listens for the
InfiniteScroll
event and loads social media embed scripts (Twitter, Facebook, Instagram, TikTok) as needed. - It uses an extensible configuration (
infinite-scroll/embed-configurations.js
) to define which scripts to load and under what conditions.
- Listens for the
infinite-scroll/embed-configurations.js
:- Defines the configuration for loading embed scripts.
- Allows extending and modifying the configuration from other modules.
infinite-scroll/dom-utils.js
- a util that can be used to initialize JS code (ads, carousels, etc.) both for the current article and for subsequently loaded articles. Read more.
InfiniteScrollManager
:- Handles requests to Elasticsearch to obtain additional pages. By default, the query is searching for the same category and template.
InfiniteScrollController
andInfiniteScroll/index.html.twig
:- Generate the JSON that contains the information of the pages to be loaded in the infinite scroll.
Functioning Flow
Initialization:
infinite-scroll/infinite-scroll.js
is initialized on our project, configuring theIntersectionObserver
and loading the information of the current page and the following ones from the JSON generated byInfiniteScrollController
. Example of how initialize infinite scroll on the projectsrc/App/Bundle/CmsBundle/Resources/public/javascripts/app/article.js
. Just pass as parameter in the initialize function the querySelector where intersectionObserver has to watch to load new articles
jsrequire([ "../src/main", "wfcb/infinite-scroll/index", "wfcb/infinite-scroll/embed-loader", ], function (_, infiniteScroll) { document.addEventListener("DOMContentLoaded", function () { infiniteScroll.initialize("footer"); }); });
Scroll Detection:
- When the user scrolls and the
IntersectionObserver
detects that the end of the page is visible, the loading of new content is activated.
- When the user scrolls and the
Content Request:
infinite-scroll/infinite-scroll.js
uses the JSON generated byInfiniteScrollController::indexAction
(that, in turn, obtains this data fromInfiniteScrollManager::getInfiniteScrollData
) to obtain the following pages. The JSON generated is similar to this:
html<div data-type="infiniteScroll"> <script type="application/json"> { "currentPage":{ "url":"http://primicias.local:8082/prueba/tce-anula-proceso-destitucion-john-vinueza-alcalde-riobamba-437/", "id":437, "hiddenElements":[ "header", ".c-footer" ], "insertAfterSelector":"main", "shownSelectors":"section", "title":"TCE anula proceso de destitución contra John Vinueza, alcalde de Riobamba" }, "nextPages":[ { "url":"http://primicias.local:8082/prueba/noticia-embeds-439/", "id":439, "hiddenElements":[ "header", ".c-footer" ], "insertAfterSelector":"main", "shownSelectors":"section" }, // ...Rest of pages, 5 by default ]} </script> </div>
- Explanation:
url
: URL of the page.id
: Page ID.hiddenElements
: Selectors of elements to hide in the new content.insertAfterSelector
: Selector of the element after which to insert the new content.shownSelectors
: Selector of the element containing the new content.title
: Page title.
The data for each page is being composed by
InfiniteScrollManager::getPageData
. Check this service's code in order to see how to overwrite/extend the data that is returned.Content Insertion:
- The new content is inserted into the page after the last article.
- The event “InfiniteScroll” is dispatched with the document fragment loaded.
Embed Loading:
infinite-scroll/dom-utils.js
listens for theInfiniteScroll
event and load scripts previously set in your application. For example we have our main.js like this and we want to load carousels in the loaded pages:
jsvar initializeCarousels = function (element) { fnCarousel1items(element); fnCarouselTemasDetacados(element); fnCarousel1itemsSponsoredContent(element); fnCarousel1itemsOnlyDots(element); fnCarouselVideosRecomendados(element); fnCarousel1itemsSpecialsCardsRelevant(element); fnCarousel3items(element); fnCarousel4items(element); }; var initializeInteractions = function (element) { openSideBar.init(element); closeSideBar.init(element); toggleSubmenu.init(element); scrollToTop.init(element); openModals.init(element); closeModals.init(element); closeModalsOutside.init(element); closeModalsPresEsc.init(element); }; var initializeCookies = function (element) { generateCookies(element); }; $(document).ready(function () { initializeCarousels(document); initializeInteractions(document); initializeCookies(document); });
We can import
infinite-scroll/dom-utils
jsdefine([ "wfcb/infinite-scroll/dom-utils", ], function (domUtils) //Rest of code.. //Add this lines and call methods you want to reload in the new pages laoded domUtils.forEach("article", initializeCarousels); domUtils.forEach("article", initializeInteractions);
infinite-scroll/embed-scripts.js
listens for theInfiniteScroll
event and, if it finds social media embeds in the new content, loads the necessary scripts.
- URL / Title Update:
- The URL and title is updated to reflect the current page in the scroll.
Extensibility
- Embeds:
- The embed loading configuration in
infinite-scroll/embed-configurations.js
can be extended or modified from other modules.
- The embed loading configuration in
- Events:
- Other modules can listen for the
InfiniteScroll
event to perform additional actions when new content is loaded.
- Other modules can listen for the
- Scroll pages data:
InfiniteScrollManager::getPageData
receives thePageArticle
argument and returns the array that will be exposed to JS in JSON format.
Usage
To integrate infinite scroll into a project, make sure that:
The JSON generated by
InfiniteScrollController
is available on the page. overwrite the base block insrc/App/Bundle/CmsBundle/Resources/views/Article/show.html.twig
.{% block wf_inifinte_scroll %} {{ render_esi( url('wf_article_next_article', { page: page.id, category: category.id, template: page.template, numPages: 5 }) ) }} {% endblock %}
The js files are loaded property in
article.js
of your project.
require([
"wfcb/infinite-scroll/index",
"wfcb/infinite-scroll/embed-loader",
], function (infiniteScroll) {
document.addEventListener("DOMContentLoaded", function () {
infiniteScroll.initialize("footer");
});
});
- The HTML elements that contains the social media embeds contains the necesary classes to be detected by the
infinite-scroll/embed-scripts.js
file.
dom-utils
Intro
infinite-scroll/dom-utils.js
is a small utility that makes it easier to initialize JavaScript code (e.g.: ads, carousels, etc.) both for the currently loaded article and also for subsequently loaded articles.
It applies the given callback for the existing HTML, loaded from the server, and also listens for the InfiniteScroll
event to apply the same functions for the articles that are loaded through AJAX as part of infinite scroll.
Usage
forEach
domUtils.forEach(selector, callback)
: invokes callback
for all the elements that match selector
. It invokes it first for the elements in the initial article and calls callback
for every article that is loaded through AJAX as part of initinite scroll.
define(["wfcb/infinite-scroll/dom-utils",], function(domUtils) {
domUtils.forEach(".gallery", function(galleryEl) {
// initialize the gallery for this element
})
})
on
domUtils.on(eventName, selector, callback)
: invokes callback
whenever eventName
is trigered on elements that match selector
. It initially hooks up all the elements for the initial article and also for articles that are subsequently loaded through AJAX as part of infinite scroll:
define(["wfcb/infinite-scroll/dom-utils",], function(domUtils) {
domUtils.on("click", ".zoom-button", function(zoomButtonEl) {
// the `.zoom-button` has been clicked,
// open a modal for the image associated to it
})
})