diff --git a/apps/javascript/src/challenges/infinite-scroll/index.html b/apps/javascript/src/challenges/infinite-scroll/index.html index 12a8969a4..a02ad5a7a 100644 --- a/apps/javascript/src/challenges/infinite-scroll/index.html +++ b/apps/javascript/src/challenges/infinite-scroll/index.html @@ -1,6 +1,8 @@ - + + + @@ -11,6 +13,12 @@

Loading...

+
+

End of content

+
+
+

Error occured while fetching posts, try refreshing!

+
diff --git a/apps/javascript/src/challenges/infinite-scroll/index.js b/apps/javascript/src/challenges/infinite-scroll/index.js index d471da07c..63353d6b6 100644 --- a/apps/javascript/src/challenges/infinite-scroll/index.js +++ b/apps/javascript/src/challenges/infinite-scroll/index.js @@ -1,59 +1,121 @@ +// HTML ELEMENTS const container = document.querySelector('.post-container'); const loader = document.querySelector('.loader'); -let start = 0; -let end = start + 14; -let posts = []; +const endOfContentEl = document.querySelector('.end-of-content'); +const errorEl = document.querySelector('.fetch-error'); + +// LOCAL STATE +let startIndex = 0; +let endIndex = getNextPostsCount(startIndex); let isFetching = false; +let isError = false; +let endOfContent = false; +let attempt = 0; +const MAX_RETRIES = 3; + +// Calculate window's height and get posts count based on start number +function getNextPostsCount(start) { + const postHeight = 90; + const newPostCount = Math.ceil(window.innerHeight / postHeight); + return start + newPostCount; +} -//Add Posts to DOM -function addPosts() { - posts.forEach((post, ind) => { - const postContainer = document.createElement('p'); +// Add Posts to DOM +function addPosts(posts = []) { + posts.forEach((post, index) => { + const postContainer = document.createElement('div'); postContainer.className = 'post'; - const postContent = document.createTextNode(post.body + ind); - postContainer.appendChild(postContent); + + const postNumberEl = document.createElement('span'); + postNumberEl.className = 'post-number'; + postNumberEl.textContent = startIndex + index + 1; + + const postContentEl = document.createElement('span'); + postContentEl.className = 'post-body'; + postContentEl.textContent = post.body; + + postContainer.appendChild(postNumberEl); + postContainer.appendChild(postContentEl); container.appendChild(postContainer); }); } -function handleLoader(loaderStatus) { - loader.style.display = loaderStatus; +// Show end of content on DOM +function showEndContent() { + endOfContentEl.style.display = 'block'; } -//api call -const url = `https://jsonplaceholder.typicode.com/posts?_start=${start}&_end=${end}`; +function toggleError(display) { + errorEl.style.display = display; +} + +// Show/Hide Loading text on DOM +function toggleLoader(loaderStatus) { + loader.style.display = loaderStatus; +} -function getPosts() { +// Fetch posts by start and end numbers from server +function fetchPostsApi(start, end) { + const url = `https://jsonplaceholder.typicode.com/posts?_start=${start}&_end=${end}`; isFetching = true; - handleLoader('block'); + toggleError('none'); + toggleLoader('block'); setTimeout(async () => { try { const res = await fetch(url); - const json = await res.json(); - posts = json; - addPosts(posts); - start = end; - end = start + 14; + const posts = await res.json(); + + // End of content when posts are less than requested posts + if (posts.length < end - start) { + endOfContent = true; + toggleLoader('none'); + if (posts.length > 0) { + addPosts(posts); + } + showEndContent(); + } else { + addPosts(posts); + startIndex = end; + endIndex = getNextPostsCount(startIndex); + } + attempt = 0; + isError = false; } catch (err) { console.log(err); + + // Retry incase of API error + attempt++; + const renderedPosts = document.getElementsByClassName('post').length; + + if (attempt > MAX_RETRIES) { + toggleError('block'); + isError = true; + } else if (renderedPosts === 0) { + fetchPostsApi(start, end); + } + + toggleLoader('none'); } finally { isFetching = false; - handleLoader('none'); } }, 500); } -//initial Load for posts -getPosts(); +// Fetch initial posts on page load +fetchPostsApi(startIndex, endIndex); -//scroll eventListener -window.addEventListener('scroll', () => { - if (isFetching) { - handleLoader('block'); - return; +// verify app state and call fetch posts api +function checkAndGetPosts() { + if (isFetching || endOfContent || isError) return; + const scrolledHeight = Math.ceil(window.innerHeight + window.scrollY); + const docOffset = window.document.body.offsetHeight - 36; + if (scrolledHeight >= docOffset) { + fetchPostsApi(startIndex, endIndex); } +} - if (window.innerHeight + window.scrollY >= window.document.body.offsetHeight) { - getPosts(); - } -}); +//scroll eventListener +window.addEventListener('scroll', checkAndGetPosts); + +// resize eventListener +window.addEventListener('resize', checkAndGetPosts); diff --git a/apps/javascript/src/challenges/infinite-scroll/style.css b/apps/javascript/src/challenges/infinite-scroll/style.css index d86405f25..0d74c85f6 100644 --- a/apps/javascript/src/challenges/infinite-scroll/style.css +++ b/apps/javascript/src/challenges/infinite-scroll/style.css @@ -1,14 +1,39 @@ .container { - display: flex; - flex-direction: column; - padding: 1rem; + display: flex; + flex-direction: column; + padding: 1rem; } + .post { - padding: 1rem; - border: 1px solid; - border-radius: 8px; + display: flex; + align-items: center; + padding: 0.5rem; + margin-block: 1rem; + border: 1px solid; + border-radius: 8px; +} + +.post-body { + padding-inline: 1rem; +} + +.post-number { + padding: 1rem; + background-color: oldlace; + border-radius: 8px; } .loader { - text-align: center; -} \ No newline at end of file + text-align: center; +} + +.end-of-content { + display: none; + text-align: center; +} + +.fetch-error { + display: none; + text-align: center; + color: red; +}