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;
+}