Skip to content

Commit

Permalink
feat(homepage-posts): infinite scroll (#1845)
Browse files Browse the repository at this point in the history
* feat(homepage-posts): infinite scroll

* fix: globally-scoped check for infinite scroll loading

* feat: queue fetch requests

* feat: prevent queuing more than one load more per block

---------

Co-authored-by: Derrick Koo <[email protected]>
Co-authored-by: dkoo <[email protected]>
  • Loading branch information
3 people committed Sep 19, 2024
1 parent 51551e6 commit f5f1c20
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 44 deletions.
4 changes: 4 additions & 0 deletions src/blocks/homepage-articles/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
"type": "boolean",
"default": false
},
"infiniteScroll": {
"type": "boolean",
"default": false
},
"readMoreLabel": {
"type": "string",
"default": "Keep reading"
Expand Down
20 changes: 15 additions & 5 deletions src/blocks/homepage-articles/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ class Edit extends Component< HomepageArticlesProps > {
mobileStack,
minHeight,
moreButton,
infiniteScroll,
showExcerpt,
showReadMore,
readMoreLabel,
Expand Down Expand Up @@ -434,11 +435,20 @@ class Edit extends Component< HomepageArticlesProps > {
</i>
) : (
! specificMode && (
<ToggleControl
label={ __( 'Show "Load more posts" Button', 'newspack-blocks' ) }
checked={ moreButton }
onChange={ () => setAttributes( { moreButton: ! moreButton } ) }
/>
<>
<ToggleControl
label={ __( 'Show "Load more posts" Button', 'newspack-blocks' ) }
checked={ moreButton }
onChange={ () => setAttributes( { moreButton: ! moreButton } ) }
/>
{ moreButton && (
<ToggleControl
label={ __( 'Infinite Scroll', 'newspack-blocks' ) }
checked={ infiniteScroll }
onChange={ () => setAttributes( { infiniteScroll: ! infiniteScroll } ) }
/>
) }
</>
)
) }
<ToggleControl
Expand Down
123 changes: 85 additions & 38 deletions src/blocks/homepage-articles/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,27 @@ function buildLoadMoreHandler( blockWrapperEl ) {
return;
}
const postsContainerEl = blockWrapperEl.querySelector( '[data-posts]' );
const isInfiniteScroll = btnEl.getAttribute( 'data-infinite-scroll' );

// Set initial state flags.
let isFetching = false;
window.newspackBlocksIsFetching = window.newspackBlocksIsFetching || false;
window.newspackBlocksFetchQueue = window.newspackBlocksFetchQueue || [];
let isEndOfData = false;
let isPending = false;

btnEl.addEventListener( 'click', () => {
// Early return if still fetching or no more posts to render.
if ( isFetching || isEndOfData ) {
return false;
const maybeLoadMore = () => {
if ( isPending ) {
return;
}
isPending = true;
loadMore();
};

isFetching = true;
const loadMore = () => {
// Early return if no more posts to render.
if ( isEndOfData ) {
return false;
}

blockWrapperEl.classList.remove( 'is-error' );
blockWrapperEl.classList.add( 'is-loading' );
Expand All @@ -55,48 +64,86 @@ function buildLoadMoreHandler( blockWrapperEl ) {
const requestURL =
btnEl.getAttribute( 'data-next' ) + '&exclude_ids=' + getRenderedPostsIds().join( ',' );

// If there's already a fetch in progress, queue this one to run after it ends.
if ( window.newspackBlocksIsFetching ) {
window.newspackBlocksFetchQueue.push( loadMore );
return false;
}

window.newspackBlocksIsFetching = true;
fetchWithRetry( { url: requestURL, onSuccess, onError }, fetchRetryCount );
};

/**
* @param {Object} data Post data
*/
function onSuccess( data ) {
// Validate received data.
if ( ! isPostsDataValid( data ) ) {
return onError();
}
/**
* @param {Object} data Post data
*/
function onSuccess( data ) {
// Validate received data.
if ( ! isPostsDataValid( data ) ) {
return onError();
}

if ( data.items.length ) {
// Render posts' HTML from string.
const postsHTML = data.items.map( item => item.html ).join( '' );
postsContainerEl.insertAdjacentHTML( 'beforeend', postsHTML );
}
if ( data.items.length ) {
// Render posts' HTML from string.
const postsHTML = data.items.map( item => item.html ).join( '' );
postsContainerEl.insertAdjacentHTML( 'beforeend', postsHTML );
}

if ( data.next ) {
// Save next URL as button's attribute.
btnEl.setAttribute( 'data-next', data.next );
}
if ( data.next ) {
// Save next URL as button's attribute.
btnEl.setAttribute( 'data-next', data.next );
}

if ( ! data.items.length || ! data.next ) {
isEndOfData = true;
blockWrapperEl.classList.remove( 'has-more-button' );
}
if ( ! data.items.length || ! data.next ) {
isEndOfData = true;
blockWrapperEl.classList.remove( 'has-more-button' );
}

isFetching = false;
onEnd();
}

blockWrapperEl.classList.remove( 'is-loading' );
}
/**
* Handle fetching error
*/
function onError() {
blockWrapperEl.classList.add( 'is-error' );
onEnd();
}

/**
* Handle fetching error
*/
function onError() {
isFetching = false;
/**
* Callback to run after a fetch request is completed.
*/
function onEnd() {
window.newspackBlocksIsFetching = false;
blockWrapperEl.classList.remove( 'is-loading' );

blockWrapperEl.classList.remove( 'is-loading' );
blockWrapperEl.classList.add( 'is-error' );
// If there are queued fetches, run the next one.
if ( window.newspackBlocksFetchQueue.length ) {
window.newspackBlocksFetchQueue.shift()();
}
} );
isPending = false;
}

btnEl.addEventListener( 'click', maybeLoadMore );

if ( isInfiniteScroll ) {
// Create an intersection observer instance
const btnObserver = new IntersectionObserver(
entries => {
entries.forEach( entry => {
if ( entry.isIntersecting ) {
maybeLoadMore();
}
} );
},
{
root: null,
rootMargin: '0px',
threshold: 1,
}
);
btnObserver.observe( btnEl );
}
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/blocks/homepage-articles/view.php
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,12 @@ class="<?php echo esc_attr( $classes ); ?>"
<?php

if ( $has_more_button ) :
$load_more = '';
if ( (bool) $attributes['infiniteScroll'] ) {
$load_more = 'data-infinite-scroll="true"';
}
?>
<button type="button" class="wp-block-button__link" data-next="<?php echo esc_url( $articles_rest_url ); ?>">
<button type="button" class="wp-block-button__link" <?php echo esc_attr( $load_more ); ?> data-next="<?php echo esc_url( $articles_rest_url ); ?>">
<span class="label">
<?php
if ( ! empty( $attributes['moreButtonText'] ) ) {
Expand Down
1 change: 1 addition & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ declare global {
imageShape: string;
minHeight: integer;
moreButton: boolean;
infiniteScroll: boolean;
moreButtonText: string;
showAuthor: boolean;
showAvatar: boolean;
Expand Down

0 comments on commit f5f1c20

Please sign in to comment.