mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat(curriculum): add async/await to workshop-fcc-authors-page (#66972)
Signed-off-by: Naomi Carrigan <naomi@freecodecamp.org> Co-authored-by: Naomi Carrigan <naomi@freecodecamp.org>
This commit is contained in:
-149
@@ -11,8 +11,6 @@ One more thing. If you keep clicking the `Load More Authors` button until there'
|
|||||||
|
|
||||||
Access the `style` property of the `Load More Authors` button and set `cursor` to `"not-allowed"`.
|
Access the `style` property of the `Load More Authors` button and set `cursor` to `"not-allowed"`.
|
||||||
|
|
||||||
With that, your author page is complete!
|
|
||||||
|
|
||||||
# --before-all--
|
# --before-all--
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@@ -189,150 +187,3 @@ const displayAuthors = (authors) => {
|
|||||||
|
|
||||||
loadMoreBtn.addEventListener('click', fetchMoreAuthors);
|
loadMoreBtn.addEventListener('click', fetchMoreAuthors);
|
||||||
```
|
```
|
||||||
|
|
||||||
# --solutions--
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>freeCodeCamp News Author Page</title>
|
|
||||||
<link rel="stylesheet" href="./styles.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1 class="title">freeCodeCamp News Author Page</h1>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<div id="author-container"></div>
|
|
||||||
<button class="btn" id="load-more-btn">Load More Authors</button>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<script src="./script.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
```css
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--main-bg-color: #1b1b32;
|
|
||||||
--light-grey: #f5f6f7;
|
|
||||||
--dark-purple: #5a01a7;
|
|
||||||
--golden-yellow: #feac32;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--main-bg-color);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
color: var(--light-grey);
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#author-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-card {
|
|
||||||
border-radius: 15px;
|
|
||||||
width: 300px;
|
|
||||||
height: 350px;
|
|
||||||
background-color: var(--light-grey);
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-img {
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.purple-divider {
|
|
||||||
background-color: var(--dark-purple);
|
|
||||||
width: 100%;
|
|
||||||
height: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-name {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bio {
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-msg {
|
|
||||||
color: var(--light-grey);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
cursor: pointer;
|
|
||||||
width: 200px;
|
|
||||||
margin: 10px;
|
|
||||||
color: var(--main-bg-color);
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: var(--golden-yellow);
|
|
||||||
background-image: linear-gradient(#fecc4c, #ffac33);
|
|
||||||
border-color: var(--golden-yellow);
|
|
||||||
border-width: 3px;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
const authorContainer = document.getElementById('author-container');
|
|
||||||
const loadMoreBtn = document.getElementById('load-more-btn');
|
|
||||||
|
|
||||||
let startingIndex = 0;
|
|
||||||
let endingIndex = 8;
|
|
||||||
let authorDataArr = [];
|
|
||||||
|
|
||||||
fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json')
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data) => {
|
|
||||||
authorDataArr = data;
|
|
||||||
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
authorContainer.innerHTML = '<p class="error-msg">There was an error loading the authors</p>';
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchMoreAuthors = () => {
|
|
||||||
startingIndex += 8;
|
|
||||||
endingIndex += 8;
|
|
||||||
|
|
||||||
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
|
||||||
if (authorDataArr.length <= endingIndex) {
|
|
||||||
loadMoreBtn.disabled = true;
|
|
||||||
loadMoreBtn.style.cursor = "not-allowed"
|
|
||||||
loadMoreBtn.textContent = 'No more data to load';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const displayAuthors = (authors) => {
|
|
||||||
authors.forEach(({ author, image, url, bio }, index) => {
|
|
||||||
authorContainer.innerHTML += `
|
|
||||||
<div id="${index}" class="user-card">
|
|
||||||
<h2 class="author-name">${author}</h2>
|
|
||||||
<img class="user-img" src="${image}" alt="${author} avatar" />
|
|
||||||
<div class="purple-divider"></div>
|
|
||||||
<p class="bio">${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}</p>
|
|
||||||
<a class="author-link" href="${url}" target="_blank">${author} author page</a>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
loadMoreBtn.addEventListener('click', fetchMoreAuthors);
|
|
||||||
```
|
|
||||||
|
|||||||
+239
@@ -0,0 +1,239 @@
|
|||||||
|
---
|
||||||
|
id: 69df434b56d2e0faf1a7424c
|
||||||
|
title: Step 25
|
||||||
|
challengeType: 0
|
||||||
|
dashedName: step-25
|
||||||
|
---
|
||||||
|
|
||||||
|
# --description--
|
||||||
|
|
||||||
|
Now that your fCC Authors Page is fully functional, let's refactor it to improve its readability by using `async` and `await` instead of the promise chaining method `then`.
|
||||||
|
|
||||||
|
Recall that in order to use the `await` operator to wait for a function that returns a `Promise`, you need to wrap the function call in an `async` function (if it is not in the main body of a module).
|
||||||
|
|
||||||
|
Since your `fetch` call is not defined in either an asynchronous function nor in the main body of a module, you cannot use `await` before fixing that.
|
||||||
|
|
||||||
|
Wrap the `fetch` statement and the entire sequence of chained then and catch methods as a whole in a new asynchronous arrow function called `initialFetch` which takes no arguments.
|
||||||
|
|
||||||
|
An example of an `async` function declaration is:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const functionName = async () => {}
|
||||||
|
```
|
||||||
|
|
||||||
|
# --before-all--
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.fetch = () => Promise.resolve({json: () => Promise.resolve([{ author: 'Whoever', image: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', url: "http://not-a-real-url.nowhere/", bio: 'words go here' }])});
|
||||||
|
```
|
||||||
|
|
||||||
|
# --hints--
|
||||||
|
|
||||||
|
`initialFetch` should be a function.
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.isFunction(initialFetch)
|
||||||
|
```
|
||||||
|
|
||||||
|
You should use `const` to create an `initialFetch` function.
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.match(code, /const\s+initialFetch\s*=\s*/)
|
||||||
|
```
|
||||||
|
|
||||||
|
`initialFetch` should be an async function.
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.isFunction(initialFetch)
|
||||||
|
assert.match(code, /const\s+initialFetch\s*=\s*async\s*/)
|
||||||
|
```
|
||||||
|
|
||||||
|
Your `initialFetch` function should not take any parameter.
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.match(code, /const\s+initialFetch\s*=\s*.*\s*\(\s*\)\s*/)
|
||||||
|
```
|
||||||
|
|
||||||
|
Your `initialFetch` function should use arrow syntax.
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.match(code, /const\s+initialFetch\s*=\s*.*\s*\(.*\)\s*=>\s*/)
|
||||||
|
```
|
||||||
|
|
||||||
|
Your `initialFetch` function should not be empty.
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.isFunction(initialFetch)
|
||||||
|
assert.notMatch(code, /const\s+initialFetch\s*=\s*.*\s*\(\s*\)\s*=>\s*\{\s*\}/)
|
||||||
|
```
|
||||||
|
|
||||||
|
Your `initialFetch` function should contain the `fetch` statement and the chained methods.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const initialFetchBlockRegex =
|
||||||
|
/const\s+initialFetch\s*=\s*async\s*\(\s*\)\s*=>\s*\{([\s\S]*?)\}\s*$/m;
|
||||||
|
|
||||||
|
const match = code.match(initialFetchBlockRegex);
|
||||||
|
|
||||||
|
assert.isDefined(match);
|
||||||
|
|
||||||
|
const initialFetchBlockContents = match[1].trim();
|
||||||
|
const fetchStatementRegex = /^fetch\(\s*(['"`])https:\/\/cdn\.freecodecamp\.org\/curriculum\/news-author-page\/authors\.json\1\s*\)\s*\.then\(\s*(?:\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(?:\(\s*data\s*\)|data)\s*=>\s*\{\s*authorDataArr\s*=\s*data;?\s*displayAuthors\(\s*authorDataArr\.slice\(\s*startingIndex\s*,\s*endingIndex\s*\)\s*\)\s*;?\s*\}\s*\)\s*\.catch\(\s*(?:\(\s*err\s*\)|err)\s*=>\s*\{?\s*authorContainer\.innerHTML\s*=\s*(['"`])<p\s+class\s*=\s*(['"`])error-msg\3>\s*There\s+was\s+an\s+error\s+loading\s+the\s+authors\s*<\/p>\s*\2\s*;?\s*\}?\s*\)\s*;?/;
|
||||||
|
|
||||||
|
assert.isTrue(fetchStatementRegex.test(initialFetchBlockContents));
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# --seed--
|
||||||
|
|
||||||
|
## --seed-contents--
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>freeCodeCamp News Author Page</title>
|
||||||
|
<link rel="stylesheet" href="./styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 class="title">freeCodeCamp News Author Page</h1>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div id="author-container"></div>
|
||||||
|
<button class="btn" id="load-more-btn">Load More Authors</button>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="./script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--main-bg-color: #1b1b32;
|
||||||
|
--light-grey: #f5f6f7;
|
||||||
|
--dark-purple: #5a01a7;
|
||||||
|
--golden-yellow: #feac32;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--main-bg-color);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--light-grey);
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#author-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-card {
|
||||||
|
border-radius: 15px;
|
||||||
|
width: 300px;
|
||||||
|
height: 350px;
|
||||||
|
background-color: var(--light-grey);
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-img {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple-divider {
|
||||||
|
background-color: var(--dark-purple);
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-name {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-msg {
|
||||||
|
color: var(--light-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 200px;
|
||||||
|
margin: 10px;
|
||||||
|
color: var(--main-bg-color);
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: var(--golden-yellow);
|
||||||
|
background-image: linear-gradient(#fecc4c, #ffac33);
|
||||||
|
border-color: var(--golden-yellow);
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const authorContainer = document.getElementById('author-container');
|
||||||
|
const loadMoreBtn = document.getElementById('load-more-btn');
|
||||||
|
|
||||||
|
let startingIndex = 0;
|
||||||
|
let endingIndex = 8;
|
||||||
|
let authorDataArr = [];
|
||||||
|
|
||||||
|
--fcc-editable-region--
|
||||||
|
|
||||||
|
fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
authorDataArr = data;
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
authorContainer.innerHTML = '<p class="error-msg">There was an error loading the authors</p>';
|
||||||
|
});
|
||||||
|
|
||||||
|
--fcc-editable-region--
|
||||||
|
|
||||||
|
const fetchMoreAuthors = () => {
|
||||||
|
startingIndex += 8;
|
||||||
|
endingIndex += 8;
|
||||||
|
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
if (authorDataArr.length <= endingIndex) {
|
||||||
|
loadMoreBtn.disabled = true;
|
||||||
|
loadMoreBtn.style.cursor = "not-allowed"
|
||||||
|
loadMoreBtn.textContent = 'No more data to load';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayAuthors = (authors) => {
|
||||||
|
authors.forEach(({ author, image, url, bio }, index) => {
|
||||||
|
authorContainer.innerHTML += `
|
||||||
|
<div id="${index}" class="user-card">
|
||||||
|
<h2 class="author-name">${author}</h2>
|
||||||
|
<img class="user-img" src="${image}" alt="${author} avatar">
|
||||||
|
<div class="purple-divider"></div>
|
||||||
|
<p class="bio">${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}</p>
|
||||||
|
<a class="author-link" href="${url}" target="_blank">${author} author page</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
loadMoreBtn.addEventListener('click', fetchMoreAuthors);
|
||||||
|
```
|
||||||
+175
@@ -0,0 +1,175 @@
|
|||||||
|
---
|
||||||
|
id: 69e14a2fd8ecf9210c0739ee
|
||||||
|
title: Step 26
|
||||||
|
challengeType: 0
|
||||||
|
dashedName: step-26
|
||||||
|
---
|
||||||
|
|
||||||
|
# --description--
|
||||||
|
|
||||||
|
When calling an asynchronous function, you can either await the function's return or you can let it run asynchronously. If you had any code that depends on the `initialFetch` data being returned, you would want to await the function call, but since you don't have any such dependency, you can go ahead and just call the function asynchronously.
|
||||||
|
|
||||||
|
Add a function call to `initialFetch` in the indicated space in the editor.
|
||||||
|
|
||||||
|
# --hints--
|
||||||
|
|
||||||
|
You should call `initialFetch` without any arguments.
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.match(code, /^initialFetch\(\s*\)/m);
|
||||||
|
```
|
||||||
|
|
||||||
|
# --seed--
|
||||||
|
|
||||||
|
## --seed-contents--
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>freeCodeCamp News Author Page</title>
|
||||||
|
<link rel="stylesheet" href="./styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 class="title">freeCodeCamp News Author Page</h1>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div id="author-container"></div>
|
||||||
|
<button class="btn" id="load-more-btn">Load More Authors</button>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="./script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--main-bg-color: #1b1b32;
|
||||||
|
--light-grey: #f5f6f7;
|
||||||
|
--dark-purple: #5a01a7;
|
||||||
|
--golden-yellow: #feac32;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--main-bg-color);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--light-grey);
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#author-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-card {
|
||||||
|
border-radius: 15px;
|
||||||
|
width: 300px;
|
||||||
|
height: 350px;
|
||||||
|
background-color: var(--light-grey);
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-img {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple-divider {
|
||||||
|
background-color: var(--dark-purple);
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-name {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-msg {
|
||||||
|
color: var(--light-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 200px;
|
||||||
|
margin: 10px;
|
||||||
|
color: var(--main-bg-color);
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: var(--golden-yellow);
|
||||||
|
background-image: linear-gradient(#fecc4c, #ffac33);
|
||||||
|
border-color: var(--golden-yellow);
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const authorContainer = document.getElementById('author-container');
|
||||||
|
const loadMoreBtn = document.getElementById('load-more-btn');
|
||||||
|
|
||||||
|
let startingIndex = 0;
|
||||||
|
let endingIndex = 8;
|
||||||
|
let authorDataArr = [];
|
||||||
|
|
||||||
|
const initialFetch = async () => {
|
||||||
|
fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
authorDataArr = data;
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
authorContainer.innerHTML = '<p class="error-msg">There was an error loading the authors</p>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchMoreAuthors = () => {
|
||||||
|
startingIndex += 8;
|
||||||
|
endingIndex += 8;
|
||||||
|
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
if (authorDataArr.length <= endingIndex) {
|
||||||
|
loadMoreBtn.disabled = true;
|
||||||
|
loadMoreBtn.style.cursor = "not-allowed"
|
||||||
|
loadMoreBtn.textContent = 'No more data to load';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayAuthors = (authors) => {
|
||||||
|
authors.forEach(({ author, image, url, bio }, index) => {
|
||||||
|
authorContainer.innerHTML += `
|
||||||
|
<div id="${index}" class="user-card">
|
||||||
|
<h2 class="author-name">${author}</h2>
|
||||||
|
<img class="user-img" src="${image}" alt="${author} avatar">
|
||||||
|
<div class="purple-divider"></div>
|
||||||
|
<p class="bio">${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}</p>
|
||||||
|
<a class="author-link" href="${url}" target="_blank">${author} author page</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
--fcc-editable-region--
|
||||||
|
|
||||||
|
--fcc-editable-region--
|
||||||
|
loadMoreBtn.addEventListener('click', fetchMoreAuthors);
|
||||||
|
```
|
||||||
+201
@@ -0,0 +1,201 @@
|
|||||||
|
---
|
||||||
|
id: 69e16a610b33e847d85be16e
|
||||||
|
title: Step 27
|
||||||
|
challengeType: 0
|
||||||
|
dashedName: step-27
|
||||||
|
---
|
||||||
|
|
||||||
|
# --description--
|
||||||
|
|
||||||
|
At this point, the behaviour of the app should not have changed. The code still fetches a file from the CDN and then processes the retrieved data into `JSON`. Once that's done, it saves that data into the `authorDataArr` variable and then calls `displayAuthors`.
|
||||||
|
|
||||||
|
Modify the call to `fetch` so that it uses the `await` operator. Save the result in a new `const` variable called `res`. The new variable should be locally defined within `initialFetch`.
|
||||||
|
|
||||||
|
Given this code:
|
||||||
|
|
||||||
|
```js
|
||||||
|
fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json')
|
||||||
|
```
|
||||||
|
|
||||||
|
You would write:
|
||||||
|
|
||||||
|
```js
|
||||||
|
await fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json')
|
||||||
|
```
|
||||||
|
|
||||||
|
You may see a `SyntaxError`. This can be ignored for now.
|
||||||
|
|
||||||
|
# --before-all--
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.fetch = () => Promise.resolve({json: () => Promise.resolve([{ author: 'Whoever', image: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', url: "http://not-a-real-url.nowhere/", bio: 'words go here' }])});
|
||||||
|
```
|
||||||
|
|
||||||
|
# --hints--
|
||||||
|
|
||||||
|
You should use the `await` keyword to await the `fetch` call.
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.match(code, /await\s+fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)/)
|
||||||
|
```
|
||||||
|
|
||||||
|
You should save the result of the awaited `fetch` call in a local `const` variable called `res`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.match(code, /^\s*const\s+res\s*=\s*await\s+fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)/m)
|
||||||
|
```
|
||||||
|
|
||||||
|
# --seed--
|
||||||
|
|
||||||
|
## --seed-contents--
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>freeCodeCamp News Author Page</title>
|
||||||
|
<link rel="stylesheet" href="./styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 class="title">freeCodeCamp News Author Page</h1>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div id="author-container"></div>
|
||||||
|
<button class="btn" id="load-more-btn">Load More Authors</button>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="./script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--main-bg-color: #1b1b32;
|
||||||
|
--light-grey: #f5f6f7;
|
||||||
|
--dark-purple: #5a01a7;
|
||||||
|
--golden-yellow: #feac32;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--main-bg-color);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--light-grey);
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#author-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-card {
|
||||||
|
border-radius: 15px;
|
||||||
|
width: 300px;
|
||||||
|
height: 350px;
|
||||||
|
background-color: var(--light-grey);
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-img {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple-divider {
|
||||||
|
background-color: var(--dark-purple);
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-name {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-msg {
|
||||||
|
color: var(--light-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 200px;
|
||||||
|
margin: 10px;
|
||||||
|
color: var(--main-bg-color);
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: var(--golden-yellow);
|
||||||
|
background-image: linear-gradient(#fecc4c, #ffac33);
|
||||||
|
border-color: var(--golden-yellow);
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const authorContainer = document.getElementById('author-container');
|
||||||
|
const loadMoreBtn = document.getElementById('load-more-btn');
|
||||||
|
|
||||||
|
let startingIndex = 0;
|
||||||
|
let endingIndex = 8;
|
||||||
|
let authorDataArr = [];
|
||||||
|
|
||||||
|
const initialFetch = async () => {
|
||||||
|
--fcc-editable-region--
|
||||||
|
fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json')
|
||||||
|
--fcc-editable-region--
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
authorDataArr = data;
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
authorContainer.innerHTML = '<p class="error-msg">There was an error loading the authors</p>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchMoreAuthors = () => {
|
||||||
|
startingIndex += 8;
|
||||||
|
endingIndex += 8;
|
||||||
|
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
if (authorDataArr.length <= endingIndex) {
|
||||||
|
loadMoreBtn.disabled = true;
|
||||||
|
loadMoreBtn.style.cursor = "not-allowed"
|
||||||
|
loadMoreBtn.textContent = 'No more data to load';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayAuthors = (authors) => {
|
||||||
|
authors.forEach(({ author, image, url, bio }, index) => {
|
||||||
|
authorContainer.innerHTML += `
|
||||||
|
<div id="${index}" class="user-card">
|
||||||
|
<h2 class="author-name">${author}</h2>
|
||||||
|
<img class="user-img" src="${image}" alt="${author} avatar">
|
||||||
|
<div class="purple-divider"></div>
|
||||||
|
<p class="bio">${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}</p>
|
||||||
|
<a class="author-link" href="${url}" target="_blank">${author} author page</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
initialFetch();
|
||||||
|
loadMoreBtn.addEventListener('click', fetchMoreAuthors);
|
||||||
|
```
|
||||||
+208
@@ -0,0 +1,208 @@
|
|||||||
|
---
|
||||||
|
id: 69e1e83ac6424325f77331e8
|
||||||
|
title: Step 28
|
||||||
|
challengeType: 0
|
||||||
|
dashedName: step-28
|
||||||
|
---
|
||||||
|
|
||||||
|
# --description--
|
||||||
|
|
||||||
|
Next, change the line of code that calls the `json` method to use the `await` operator instead.
|
||||||
|
|
||||||
|
For example, given this code:
|
||||||
|
|
||||||
|
```js
|
||||||
|
.then((res) => res.json());
|
||||||
|
```
|
||||||
|
|
||||||
|
You would write:
|
||||||
|
|
||||||
|
```js
|
||||||
|
await res.json();
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, store the result in your `authorDataArr`, and move the line of code that calls `displayAuthors` so it is immediately following your new `await` statement.
|
||||||
|
Finally, delete the remaining `then` statement and its code block as it no longer serves any purpose.
|
||||||
|
|
||||||
|
You will notice a `SyntaxError` in the console at this point. The next step will deal with refactoring the `catch` method to resolve this error.
|
||||||
|
|
||||||
|
# --before-all--
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.fetch = () => Promise.resolve({json: () => Promise.resolve([{ author: 'Whoever', image: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', url: "http://not-a-real-url.nowhere/", bio: 'words go here' }])});
|
||||||
|
```
|
||||||
|
|
||||||
|
# --hints--
|
||||||
|
|
||||||
|
You should assign the awaited result of `res.json()` to the `authorDataArr` variable from the global scope
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.match(code, /^\s*authorDataArr\s*=\s*await\s+res\.json\(\s*\)\s*;?/m)
|
||||||
|
```
|
||||||
|
|
||||||
|
`displayAuthors` should still be called as before
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.match(code, /^\s*displayAuthors\(\s*authorDataArr\.slice\(\s*startingIndex\s*,\s*endingIndex\s*\)\s*\)\s*;?/m)
|
||||||
|
```
|
||||||
|
|
||||||
|
You should no longer have any chained `then` methods
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.notMatch(code, /\.then\(/);
|
||||||
|
```
|
||||||
|
|
||||||
|
# --seed--
|
||||||
|
|
||||||
|
## --seed-contents--
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>freeCodeCamp News Author Page</title>
|
||||||
|
<link rel="stylesheet" href="./styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 class="title">freeCodeCamp News Author Page</h1>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div id="author-container"></div>
|
||||||
|
<button class="btn" id="load-more-btn">Load More Authors</button>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="./script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--main-bg-color: #1b1b32;
|
||||||
|
--light-grey: #f5f6f7;
|
||||||
|
--dark-purple: #5a01a7;
|
||||||
|
--golden-yellow: #feac32;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--main-bg-color);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--light-grey);
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#author-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-card {
|
||||||
|
border-radius: 15px;
|
||||||
|
width: 300px;
|
||||||
|
height: 350px;
|
||||||
|
background-color: var(--light-grey);
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-img {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple-divider {
|
||||||
|
background-color: var(--dark-purple);
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-name {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-msg {
|
||||||
|
color: var(--light-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 200px;
|
||||||
|
margin: 10px;
|
||||||
|
color: var(--main-bg-color);
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: var(--golden-yellow);
|
||||||
|
background-image: linear-gradient(#fecc4c, #ffac33);
|
||||||
|
border-color: var(--golden-yellow);
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const authorContainer = document.getElementById('author-container');
|
||||||
|
const loadMoreBtn = document.getElementById('load-more-btn');
|
||||||
|
|
||||||
|
let startingIndex = 0;
|
||||||
|
let endingIndex = 8;
|
||||||
|
let authorDataArr = [];
|
||||||
|
|
||||||
|
const initialFetch = async () => {
|
||||||
|
const res = await fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json');
|
||||||
|
--fcc-editable-region--
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
authorDataArr = data;
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
})
|
||||||
|
--fcc-editable-region--
|
||||||
|
.catch((err) => {
|
||||||
|
authorContainer.innerHTML = '<p class="error-msg">There was an error loading the authors</p>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchMoreAuthors = () => {
|
||||||
|
startingIndex += 8;
|
||||||
|
endingIndex += 8;
|
||||||
|
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
if (authorDataArr.length <= endingIndex) {
|
||||||
|
loadMoreBtn.disabled = true;
|
||||||
|
loadMoreBtn.style.cursor = "not-allowed"
|
||||||
|
loadMoreBtn.textContent = 'No more data to load';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayAuthors = (authors) => {
|
||||||
|
authors.forEach(({ author, image, url, bio }, index) => {
|
||||||
|
authorContainer.innerHTML += `
|
||||||
|
<div id="${index}" class="user-card">
|
||||||
|
<h2 class="author-name">${author}</h2>
|
||||||
|
<img class="user-img" src="${image}" alt="${author} avatar">
|
||||||
|
<div class="purple-divider"></div>
|
||||||
|
<p class="bio">${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}</p>
|
||||||
|
<a class="author-link" href="${url}" target="_blank">${author} author page</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
initialFetch();
|
||||||
|
loadMoreBtn.addEventListener('click', fetchMoreAuthors);
|
||||||
|
```
|
||||||
+383
@@ -0,0 +1,383 @@
|
|||||||
|
---
|
||||||
|
id: 69e1f5fa037cd7c2c4d6b8eb
|
||||||
|
title: Step 29
|
||||||
|
challengeType: 0
|
||||||
|
dashedName: step-29
|
||||||
|
---
|
||||||
|
|
||||||
|
# --description--
|
||||||
|
|
||||||
|
Finally, you need to deal with the `catch` method. First, nest the `fetch` statement, the `json` statement, and the displayAuthors call into a `try` block within `initialFetch`. Then, modify the chained `catch` method to be a catch block instead as per the example shown below.
|
||||||
|
|
||||||
|
```js
|
||||||
|
try {
|
||||||
|
// code that can cause or throw an error goes in this block
|
||||||
|
} catch (error) {
|
||||||
|
// error handling code here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you've done this correctly, the `try` block will contain three lines of code in it and the `catch` block will contain just one line of code. Also, the `SyntaxError` will be resolved and the app should function normally.
|
||||||
|
|
||||||
|
Give yourself a pat on the back! You have learned how to make your asynchronous code more readable using the `async` and `await` operators! Now your author page is complete.
|
||||||
|
|
||||||
|
# --before-all--
|
||||||
|
|
||||||
|
```js
|
||||||
|
const testData = [{ author: 'Whoever', image: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', url: "http://not-a-real-url.nowhere/", bio: 'words go here' }];
|
||||||
|
window.fetch = () => Promise.resolve({json: () => Promise.resolve(testData)});
|
||||||
|
```
|
||||||
|
|
||||||
|
# --hints--
|
||||||
|
|
||||||
|
You should create a `try/catch` block
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.match(code, /try\s*\{[\s\S]*?\}\s*catch\s*\([^)]*\)\s*\{/);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `catch` should no longer be a chained method
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.notMatch(code, /\.catch/);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `try` block should contain the three statements that fetch the data, create the `json` and then display it.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const tryBlockRegex =
|
||||||
|
/try\s*\{([\s\S]*?)\}(?=\s*(catch|finally)\b|\s*$)/;
|
||||||
|
|
||||||
|
const fetchLineRegex =
|
||||||
|
/const\s+res\s*=\s*await\s+fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1,*\s*\)/;
|
||||||
|
|
||||||
|
const jsonLineRegex =
|
||||||
|
/authorDataArr\s*=\s*await\s+res\.json\(\)/;
|
||||||
|
|
||||||
|
const displayLineRegex =
|
||||||
|
/displayAuthors\(authorDataArr\.slice\(startingIndex,\s*endingIndex\)\)/;
|
||||||
|
|
||||||
|
const match = code.match(tryBlockRegex);
|
||||||
|
|
||||||
|
assert.isDefined(match);
|
||||||
|
|
||||||
|
const tryBlockContents = match[1];
|
||||||
|
|
||||||
|
assert.isTrue(fetchLineRegex.test(tryBlockContents));
|
||||||
|
assert.isTrue(jsonLineRegex.test(tryBlockContents));
|
||||||
|
assert.isTrue(displayLineRegex.test(tryBlockContents));
|
||||||
|
```
|
||||||
|
|
||||||
|
The fCC Authors Page should be rendering correctly
|
||||||
|
|
||||||
|
```js
|
||||||
|
assert.equal(document.querySelector('.author-name').innerText, `Whoever`);
|
||||||
|
assert.equal(document.querySelector('img')?.getAttribute('alt'), `Whoever avatar`);
|
||||||
|
```
|
||||||
|
|
||||||
|
# --seed--
|
||||||
|
|
||||||
|
## --seed-contents--
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>freeCodeCamp News Author Page</title>
|
||||||
|
<link rel="stylesheet" href="./styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 class="title">freeCodeCamp News Author Page</h1>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div id="author-container"></div>
|
||||||
|
<button class="btn" id="load-more-btn">Load More Authors</button>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="./script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--main-bg-color: #1b1b32;
|
||||||
|
--light-grey: #f5f6f7;
|
||||||
|
--dark-purple: #5a01a7;
|
||||||
|
--golden-yellow: #feac32;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--main-bg-color);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--light-grey);
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#author-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-card {
|
||||||
|
border-radius: 15px;
|
||||||
|
width: 300px;
|
||||||
|
height: 350px;
|
||||||
|
background-color: var(--light-grey);
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-img {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple-divider {
|
||||||
|
background-color: var(--dark-purple);
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-name {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-msg {
|
||||||
|
color: var(--light-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 200px;
|
||||||
|
margin: 10px;
|
||||||
|
color: var(--main-bg-color);
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: var(--golden-yellow);
|
||||||
|
background-image: linear-gradient(#fecc4c, #ffac33);
|
||||||
|
border-color: var(--golden-yellow);
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const authorContainer = document.getElementById('author-container');
|
||||||
|
const loadMoreBtn = document.getElementById('load-more-btn');
|
||||||
|
|
||||||
|
let startingIndex = 0;
|
||||||
|
let endingIndex = 8;
|
||||||
|
let authorDataArr = [];
|
||||||
|
|
||||||
|
const initialFetch = async () => {
|
||||||
|
--fcc-editable-region--
|
||||||
|
|
||||||
|
const res = await fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json');
|
||||||
|
authorDataArr = await res.json();
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
|
||||||
|
.catch((err) => {
|
||||||
|
authorContainer.innerHTML = '<p class="error-msg">There was an error loading the authors</p>';
|
||||||
|
});
|
||||||
|
|
||||||
|
--fcc-editable-region--
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchMoreAuthors = () => {
|
||||||
|
startingIndex += 8;
|
||||||
|
endingIndex += 8;
|
||||||
|
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
if (authorDataArr.length <= endingIndex) {
|
||||||
|
loadMoreBtn.disabled = true;
|
||||||
|
loadMoreBtn.style.cursor = "not-allowed"
|
||||||
|
loadMoreBtn.textContent = 'No more data to load';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayAuthors = (authors) => {
|
||||||
|
authors.forEach(({ author, image, url, bio }, index) => {
|
||||||
|
authorContainer.innerHTML += `
|
||||||
|
<div id="${index}" class="user-card">
|
||||||
|
<h2 class="author-name">${author}</h2>
|
||||||
|
<img class="user-img" src="${image}" alt="${author} avatar">
|
||||||
|
<div class="purple-divider"></div>
|
||||||
|
<p class="bio">${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}</p>
|
||||||
|
<a class="author-link" href="${url}" target="_blank">${author} author page</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
initialFetch();
|
||||||
|
loadMoreBtn.addEventListener('click', fetchMoreAuthors);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# --solutions--
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>freeCodeCamp News Author Page</title>
|
||||||
|
<link rel="stylesheet" href="./styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 class="title">freeCodeCamp News Author Page</h1>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div id="author-container"></div>
|
||||||
|
<button class="btn" id="load-more-btn">Load More Authors</button>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="./script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--main-bg-color: #1b1b32;
|
||||||
|
--light-grey: #f5f6f7;
|
||||||
|
--dark-purple: #5a01a7;
|
||||||
|
--golden-yellow: #feac32;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--main-bg-color);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--light-grey);
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#author-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-card {
|
||||||
|
border-radius: 15px;
|
||||||
|
width: 300px;
|
||||||
|
height: 350px;
|
||||||
|
background-color: var(--light-grey);
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-img {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple-divider {
|
||||||
|
background-color: var(--dark-purple);
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-name {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-msg {
|
||||||
|
color: var(--light-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 200px;
|
||||||
|
margin: 10px;
|
||||||
|
color: var(--main-bg-color);
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: var(--golden-yellow);
|
||||||
|
background-image: linear-gradient(#fecc4c, #ffac33);
|
||||||
|
border-color: var(--golden-yellow);
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
const authorContainer = document.getElementById("author-container");
|
||||||
|
const loadMoreBtn = document.getElementById("load-more-btn");
|
||||||
|
|
||||||
|
let startingIndex = 0;
|
||||||
|
let endingIndex = 8;
|
||||||
|
let authorDataArr = [];
|
||||||
|
|
||||||
|
const initialFetch = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
"https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json"
|
||||||
|
);
|
||||||
|
authorDataArr = await res.json();
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
} catch (err) {
|
||||||
|
authorContainer.innerHTML =
|
||||||
|
'<p class="error-msg">There was an error loading the authors</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchMoreAuthors = () => {
|
||||||
|
startingIndex += 8;
|
||||||
|
endingIndex += 8;
|
||||||
|
|
||||||
|
displayAuthors(authorDataArr.slice(startingIndex, endingIndex));
|
||||||
|
if (authorDataArr.length <= endingIndex) {
|
||||||
|
loadMoreBtn.disabled = true;
|
||||||
|
loadMoreBtn.style.cursor = "not-allowed";
|
||||||
|
loadMoreBtn.textContent = "No more data to load";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayAuthors = (authors) => {
|
||||||
|
authors.forEach(({ author, image, url, bio }, index) => {
|
||||||
|
authorContainer.innerHTML += `
|
||||||
|
<div id="${index}" class="user-card">
|
||||||
|
<h2 class="author-name">${author}</h2>
|
||||||
|
<img class="user-img" src="${image}" alt="${author} avatar">
|
||||||
|
<div class="purple-divider"></div>
|
||||||
|
<p class="bio">${bio.length > 50 ? bio.slice(0, 50) + "..." : bio}</p>
|
||||||
|
<a class="author-link" href="${url}" target="_blank">${author} author page</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
initialFetch();
|
||||||
|
loadMoreBtn.addEventListener("click", fetchMoreAuthors);
|
||||||
|
```
|
||||||
@@ -6,102 +6,35 @@
|
|||||||
"hasEditableBoundaries": true,
|
"hasEditableBoundaries": true,
|
||||||
"dashedName": "workshop-fcc-authors-page",
|
"dashedName": "workshop-fcc-authors-page",
|
||||||
"challengeOrder": [
|
"challengeOrder": [
|
||||||
{
|
{ "id": "641d9a19bff38d34d5a5edb8", "title": "Step 1" },
|
||||||
"id": "641d9a19bff38d34d5a5edb8",
|
{ "id": "641da3c6b6fbd742bff6ee40", "title": "Step 2" },
|
||||||
"title": "Step 1"
|
{ "id": "641da42481d90c4314c99e94", "title": "Step 3" },
|
||||||
},
|
{ "id": "641da465273051435d332b15", "title": "Step 4" },
|
||||||
{
|
{ "id": "641da4b16937be43ba24c63d", "title": "Step 5" },
|
||||||
"id": "641da3c6b6fbd742bff6ee40",
|
{ "id": "641da51a9810e74411262fcc", "title": "Step 6" },
|
||||||
"title": "Step 2"
|
{ "id": "641da5462576784453146ec2", "title": "Step 7" },
|
||||||
},
|
{ "id": "641da5abaac81844a54adb03", "title": "Step 8" },
|
||||||
{
|
{ "id": "641da73b09e7f046c758e0ed", "title": "Step 9" },
|
||||||
"id": "641da42481d90c4314c99e94",
|
{ "id": "641da791d0c34a472b8d15b6", "title": "Step 10" },
|
||||||
"title": "Step 3"
|
{ "id": "641da7bfbc7f0f477438ad8a", "title": "Step 11" },
|
||||||
},
|
{ "id": "641da803d9892447d059804e", "title": "Step 12" },
|
||||||
{
|
{ "id": "641da836581c254815f785fe", "title": "Step 13" },
|
||||||
"id": "641da465273051435d332b15",
|
{ "id": "641da86294fd9f485d3c2bf0", "title": "Step 14" },
|
||||||
"title": "Step 4"
|
{ "id": "641da895fb7ec648a5bdf19c", "title": "Step 15" },
|
||||||
},
|
{ "id": "641da8db2a036048ebe6999e", "title": "Step 16" },
|
||||||
{
|
{ "id": "641da97c987a514959ada414", "title": "Step 17" },
|
||||||
"id": "641da4b16937be43ba24c63d",
|
{ "id": "641da9aceb788e49a73ebcc9", "title": "Step 18" },
|
||||||
"title": "Step 5"
|
{ "id": "641da9ea9b847a49fe6ee9b6", "title": "Step 19" },
|
||||||
},
|
{ "id": "641daa5ea050f24a7cade6e6", "title": "Step 20" },
|
||||||
{
|
{ "id": "641daa8c2c3e364ac3650b37", "title": "Step 21" },
|
||||||
"id": "641da51a9810e74411262fcc",
|
{ "id": "641daabed8d0584b1150c953", "title": "Step 22" },
|
||||||
"title": "Step 6"
|
{ "id": "641daae5e18eae4b562633e4", "title": "Step 23" },
|
||||||
},
|
{ "id": "641dab13c1b6f14b9828e6b1", "title": "Step 24" },
|
||||||
{
|
{ "id": "69df434b56d2e0faf1a7424c", "title": "Step 25" },
|
||||||
"id": "641da5462576784453146ec2",
|
{ "id": "69e14a2fd8ecf9210c0739ee", "title": "Step 26" },
|
||||||
"title": "Step 7"
|
{ "id": "69e16a610b33e847d85be16e", "title": "Step 27" },
|
||||||
},
|
{ "id": "69e1e83ac6424325f77331e8", "title": "Step 28" },
|
||||||
{
|
{ "id": "69e1f5fa037cd7c2c4d6b8eb", "title": "Step 29" }
|
||||||
"id": "641da5abaac81844a54adb03",
|
|
||||||
"title": "Step 8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641da73b09e7f046c758e0ed",
|
|
||||||
"title": "Step 9"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641da791d0c34a472b8d15b6",
|
|
||||||
"title": "Step 10"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641da7bfbc7f0f477438ad8a",
|
|
||||||
"title": "Step 11"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641da803d9892447d059804e",
|
|
||||||
"title": "Step 12"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641da836581c254815f785fe",
|
|
||||||
"title": "Step 13"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641da86294fd9f485d3c2bf0",
|
|
||||||
"title": "Step 14"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641da895fb7ec648a5bdf19c",
|
|
||||||
"title": "Step 15"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641da8db2a036048ebe6999e",
|
|
||||||
"title": "Step 16"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641da97c987a514959ada414",
|
|
||||||
"title": "Step 17"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641da9aceb788e49a73ebcc9",
|
|
||||||
"title": "Step 18"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641da9ea9b847a49fe6ee9b6",
|
|
||||||
"title": "Step 19"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641daa5ea050f24a7cade6e6",
|
|
||||||
"title": "Step 20"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641daa8c2c3e364ac3650b37",
|
|
||||||
"title": "Step 21`"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641daabed8d0584b1150c953",
|
|
||||||
"title": "Step 22"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641daae5e18eae4b562633e4",
|
|
||||||
"title": "Step 23"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "641dab13c1b6f14b9828e6b1",
|
|
||||||
"title": "Step 24"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"helpCategory": "JavaScript"
|
"helpCategory": "JavaScript"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user