mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-02-28 05:01:24 -05:00
feat(curriculum): add bookmark manager lab (#56068)
Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>
This commit is contained in:
@@ -2342,7 +2342,10 @@
|
||||
},
|
||||
"pehm": { "title": "227", "intro": [] },
|
||||
"cvsw": { "title": "228", "intro": [] },
|
||||
"qjws": { "title": "229", "intro": [] },
|
||||
"lab-bookmark-manager-app": {
|
||||
"title": "Build a Bookmark Manager App",
|
||||
"intro": ["For this lab, you will build a bookmark manager app."]
|
||||
},
|
||||
"fgbp": { "title": "230", "intro": [] },
|
||||
"quiz-local-storage-and-crud": {
|
||||
"title": "Local Storage and CRUD Quiz",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Introduction to the Build a Bookmark Manager App
|
||||
block: lab-bookmark-manager-app
|
||||
superBlock: front-end-development
|
||||
---
|
||||
|
||||
## Introduction to the Build a Bookmark Manager App
|
||||
|
||||
For this lab, you will build a bookmark manager app.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Build a Bookmark Manager App",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"blockType": "lab",
|
||||
"dashedName": "lab-bookmark-manager-app",
|
||||
"order": 229,
|
||||
"superBlock": "front-end-development",
|
||||
"challengeOrder": [{ "id": "66def5467aee701733aaf8cc", "title": "Build a Bookmark Manager App" }],
|
||||
"helpCategory": "JavaScript"
|
||||
}
|
||||
@@ -0,0 +1,789 @@
|
||||
---
|
||||
id: 66def5467aee701733aaf8cc
|
||||
title: Build a Bookmark Manager App
|
||||
challengeType: 14
|
||||
dashedName: build-a-bookmark-manager-app
|
||||
demoType: onClick
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
For this lab, all the HTML and CSS has been provided to you. You will use JavaScript to complete the Bookmark Manager app so that it can store a collection of bookmarks in the local storage and read data from it.
|
||||
|
||||
**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
|
||||
|
||||
**User Stories:**
|
||||
|
||||
1. You should have a `getBookmarks` function that returns the `bookmarks` array stored in the local storage. If the `bookmarks` key has not been set yet, the `getBookmarks` function should return an empty array.
|
||||
1. The `bookmarks` key stored in the local storage should be an array of bookmark objects. Each bookmark object should have three keys: `name`, `category`, and `url`.
|
||||
1. You should have a function named `displayOrCloseForm` that toggles the `hidden` class on `#main-section` and `#form-section`.
|
||||
1. When you click `#add-bookmark-button`, you should update the inner text of `.category-name` to be the value of the selected option from `#category-dropdown` and call `displayOrCloseForm` to display the form section and hide the main section.
|
||||
1. When you click `#close-form-button`, you should run your function to hide the form section and display the main section.
|
||||
1. When you click `#add-bookmark-button-form`, you should update the `bookmarks` key stored in the local storage by adding a bookmark object to the end of the array. The object should have `name` set to the value of the `#name` input, `category` set to the value of the selected option from the category dropdown, and `url` set to the value of the `#url` input.
|
||||
1. Once the `bookmarks` key is updated, you should reset the value of `#name` and `#url` to an empty string before running your function to hide the form section and show the main section.
|
||||
1. You should have a function named `displayOrHideCategory` that toggles the `hidden` class on `#main-section` and `#bookmark-list-section`.
|
||||
1. When you click `#view-category-button`, you should update the inner text of `.category-name` to be the value of the selected option from `#category-dropdown`, modify the inner HTML of `#category-list` according to the user stories below, and call the `displayOrHideCategory` function.
|
||||
1. If none of the bookmarks in local storage have the category, you should set the inner HTML of the `#category-list` to a `p` element with the text `No Bookmarks Found`.
|
||||
1. If one or more bookmarks in local storage have the selected category you should add a radio button with `id` and `value` attributes set to the bookmark name to the `#category-list`'s inner HTML for each of those bookmarks.
|
||||
1. Each radio button should have a corresponding label containing an anchor element with the bookmark name and the `href` attribute set to the bookmark URL.
|
||||
1. When you click `#close-list-button`, you should run your function to hide the `#bookmark-list-section` and display the main section.
|
||||
1. When you click `#delete-bookmark-button`, you should delete the bookmark corresponding to the selected radio button from the local storage and update the displayed bookmark list.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should have a `getBookmarks` function.
|
||||
|
||||
```js
|
||||
assert.isFunction(getBookmarks);
|
||||
```
|
||||
|
||||
Your `getBookmarks` function should return an array.
|
||||
|
||||
```js
|
||||
assert.isArray(getBookmarks());
|
||||
```
|
||||
|
||||
Your `getBookmarks` function should return the `bookmarks` key from `localStorage`.
|
||||
|
||||
```js
|
||||
try {
|
||||
setLocalStorage()
|
||||
const testArr = getBookmarks();
|
||||
const expected = [{name: "example1", category: "news", url: "example1.com"}, {name: "example2", category: "entertainment", url: "example2.com"}, {name: "example3", category: "work", url: "example3.com"}, {name: "example4", category: "news", url: "example4.com"}];
|
||||
assert.deepEqual(testArr, expected);
|
||||
} finally {
|
||||
resetLocalStorage();
|
||||
}
|
||||
```
|
||||
|
||||
When the `bookmarks` key is not set in `localStorage` or is an empty array, the `getBookmarks` function should return an empty array.
|
||||
|
||||
```js
|
||||
try {
|
||||
localStorage.setItem("bookmarks", JSON.stringify([]));
|
||||
assert.isEmpty(getBookmarks());
|
||||
localStorage.removeItem("bookmarks");
|
||||
assert.isEmpty(getBookmarks());
|
||||
} finally {
|
||||
resetLocalStorage();
|
||||
}
|
||||
```
|
||||
|
||||
You should have a function named `displayOrCloseForm`.
|
||||
|
||||
```js
|
||||
assert.isFunction(displayOrCloseForm);
|
||||
```
|
||||
|
||||
Your `displayOrCloseForm` function should toggle the `hidden` class on `#main-section` and `#form-section`.
|
||||
|
||||
```js
|
||||
const hidden = getHidden("form");
|
||||
displayOrCloseForm();
|
||||
const hiddenAfterCall = getHidden("form");
|
||||
assert.isDefined(hidden, hiddenAfterCall);
|
||||
assert.notEqual(hidden, hiddenAfterCall);
|
||||
```
|
||||
|
||||
When you click `#add-bookmark-button`, you should update the inner text of `.category-name` to be the value of the selected option from `#category-dropdown`.
|
||||
|
||||
```js
|
||||
addBookMarkButtonTest.dispatchEvent(new Event("click"));
|
||||
const categoryName = document.querySelector(".category-name").innerText;
|
||||
assert.strictEqual(categoryName.toLowerCase(), categoryDropdownTest.value.toLowerCase());
|
||||
```
|
||||
|
||||
When you click `#add-bookmark-button`, you should call `displayOrCloseForm` to display the form section and hide the main section.
|
||||
|
||||
```js
|
||||
assert.strictEqual(getHidden("form"), "form");
|
||||
addBookMarkButtonTest.dispatchEvent(new Event("click"));
|
||||
assert.strictEqual(getHidden("form"), "main");
|
||||
```
|
||||
|
||||
When you click `#close-form-button`, you should call `displayOrCloseForm` to hide the form section and display the main section.
|
||||
|
||||
```js
|
||||
assert.strictEqual(getHidden("form"), "main");
|
||||
closeFormButtonTest.dispatchEvent(new Event("click"));
|
||||
assert.strictEqual(getHidden("form"), "form");
|
||||
```
|
||||
|
||||
When you click `#add-bookmark-button-form`, you should update the `bookmarks` key stored in the local storage by adding an object to the end of the array. The added object should have `name` set to the value of the `#name` input, `category` set to the value of the selected option from the category dropdown, and `url` set to the value of the `#url` input.
|
||||
|
||||
```js
|
||||
setLocalStorage();
|
||||
try {
|
||||
bookmarkNameTest.value = "test example5";
|
||||
bookmarkURLTest.value = "test example5.com";
|
||||
const bookmarkObject = {
|
||||
name: bookmarkNameTest.value,
|
||||
category: categoryDropdownTest.value,
|
||||
url: bookmarkURLTest.value
|
||||
};
|
||||
const expected = [{name: "example1", category: "news", url: "example1.com"}, {name: "example2", category: "entertainment", url: "example2.com"}, {name: "example3", category: "work", url: "example3.com"}, {name: "example4", category: "news", url: "example4.com"}, bookmarkObject];
|
||||
addBookmarkButtonFormTest.dispatchEvent(new Event("click"));
|
||||
assert.deepEqual(getBookmarks(), expected);
|
||||
} finally {
|
||||
bookmarkNameTest.value = "";
|
||||
bookmarkURLTest.value = "";
|
||||
resetLocalStorage();
|
||||
}
|
||||
```
|
||||
|
||||
When you click `#add-bookmark-button-form`, you should reset the value of `#name` and `#url` to an empty string.
|
||||
|
||||
```js
|
||||
try {
|
||||
bookmarkNameTest.value = "test example2";
|
||||
bookmarkURLTest.value = "test example2.com";
|
||||
addBookmarkButtonFormTest.dispatchEvent(new Event("click"));
|
||||
assert.strictEqual(bookmarkNameTest.value, "");
|
||||
assert.strictEqual(bookmarkURLTest.value, "");
|
||||
} finally {
|
||||
resetLocalStorage()
|
||||
bookmarkNameTest.value = "";
|
||||
bookmarkURLTest.value = "";
|
||||
}
|
||||
```
|
||||
|
||||
When you click `#add-bookmark-button-form`, you should run `displayOrCloseForm` to hide the form section and show the main section.
|
||||
|
||||
```js
|
||||
try {
|
||||
bookmarkNameTest.value = "test example3";
|
||||
bookmarkURLTest.value = "test example3.com";
|
||||
addBookMarkButtonTest.dispatchEvent(new Event("click"));
|
||||
assert.strictEqual(getHidden("form"), "main");
|
||||
addBookmarkButtonFormTest.dispatchEvent(new Event("click"));
|
||||
assert.strictEqual(getHidden("form"), "form");
|
||||
} finally {
|
||||
resetLocalStorage()
|
||||
}
|
||||
```
|
||||
|
||||
You should have a function named `displayOrHideCategory`.
|
||||
|
||||
```js
|
||||
assert.isFunction(displayOrHideCategory);
|
||||
```
|
||||
|
||||
Your `displayOrHideCategory` function should toggle the `hidden` class on `#main-section` and `#bookmark-list-section`.
|
||||
|
||||
```js
|
||||
const hidden = getHidden("bookmark list");
|
||||
displayOrHideCategory();
|
||||
const hiddenAfterCall = getHidden("bookmark list");
|
||||
assert.isDefined(hidden, hiddenAfterCall)
|
||||
assert.notEqual(hidden, hiddenAfterCall)
|
||||
```
|
||||
|
||||
When you click `#view-category-button`, you should update the inner text of `.category-name` to be the value of the selected option from `#category-dropdown`.
|
||||
|
||||
```js
|
||||
viewCategoryButtonTest.dispatchEvent(new Event("click"));
|
||||
const categoryName = document.querySelector(".category-name").innerText;
|
||||
assert.strictEqual(categoryName.toLowerCase(), categoryDropdownTest.value.toLowerCase());
|
||||
```
|
||||
|
||||
When you click `#view-category-button`, you should add a `p` element with the text `No Bookmarks Found` to `#category-list`'s inner HTML if none of the bookmarks in local storage have the selected category.
|
||||
|
||||
```js
|
||||
try {
|
||||
setLocalStorage()
|
||||
categoryDropdownTest.value = "miscellaneous";
|
||||
viewCategoryButtonTest.dispatchEvent(new Event("click"));
|
||||
const categoryList = document.getElementById("category-list").innerHTML;
|
||||
assert.strictEqual(categoryList, "<p>No Bookmarks Found</p>")
|
||||
} finally {
|
||||
resetLocalStorage()
|
||||
clearCategoryList()
|
||||
}
|
||||
```
|
||||
|
||||
When you click `#view-category-button`, you should modify `#category-list`'s inner HTML by adding a radio button having the `id` and `value` attributes set to the bookmark name for each bookmark in the selected category.
|
||||
|
||||
```js
|
||||
try {
|
||||
setLocalStorage();
|
||||
categoryDropdownTest.value = "news";
|
||||
viewCategoryButtonTest.dispatchEvent(new Event("click"));
|
||||
const bookmarksDisplayed = document.querySelectorAll('#category-list input[type="radio"]');
|
||||
assert.lengthOf(bookmarksDisplayed, 2);
|
||||
assert.strictEqual(bookmarksDisplayed[0].id, "example1");
|
||||
assert.strictEqual(bookmarksDisplayed[0].value, "example1");
|
||||
assert.strictEqual(bookmarksDisplayed[1].id, "example4");
|
||||
assert.strictEqual(bookmarksDisplayed[1].value, "example4");
|
||||
} finally {
|
||||
resetLocalStorage();
|
||||
}
|
||||
```
|
||||
|
||||
Each radio button added to `#category-list`'s inner HTML should have a corresponding label containing an anchor element with the bookmark name and the `href` attribute set to the bookmark URL.
|
||||
|
||||
```js
|
||||
setLocalStorage();
|
||||
try {
|
||||
categoryDropdownTest.value = "news";
|
||||
viewCategoryButtonTest.dispatchEvent(new Event("click"));
|
||||
const bookmarkLabelsDisplayed = document.querySelectorAll('#category-list label');
|
||||
assert.lengthOf(bookmarkLabelsDisplayed, 2);
|
||||
assert.strictEqual(bookmarkLabelsDisplayed[0].htmlFor, "example1");
|
||||
assert.strictEqual(bookmarkLabelsDisplayed[1].htmlFor, "example4");
|
||||
} finally {
|
||||
resetLocalStorage();
|
||||
}
|
||||
```
|
||||
|
||||
Each `label` element should contain an anchor element with the bookmark name as text, and the `href` attribute set to the bookmark URL.
|
||||
|
||||
```js
|
||||
setLocalStorage();
|
||||
try {
|
||||
categoryDropdownTest.value = "news";
|
||||
viewCategoryButtonTest.dispatchEvent(new Event("click"));
|
||||
const bookmarkAnchorsDisplayed = document.querySelectorAll('#category-list label>a');
|
||||
assert.lengthOf(bookmarkAnchorsDisplayed, 2);
|
||||
assert.strictEqual(bookmarkAnchorsDisplayed[0].innerText, "example1");
|
||||
assert(bookmarkAnchorsDisplayed[0].href.endsWith("example1.com"));
|
||||
assert.strictEqual(bookmarkAnchorsDisplayed[1].innerText, "example4");
|
||||
assert(bookmarkAnchorsDisplayed[1].href.endsWith("example4.com"));
|
||||
} finally {
|
||||
resetLocalStorage();
|
||||
}
|
||||
```
|
||||
|
||||
When you click `#close-list-button`, you should hide `#bookmark-list-section` and display the main section.
|
||||
|
||||
```js
|
||||
viewCategoryButtonTest.dispatchEvent(new Event("click"));
|
||||
assert.strictEqual(getHidden("bookmark list"), "main");
|
||||
closeListButtonTest.dispatchEvent(new Event("click"));
|
||||
assert.strictEqual(getHidden("bookmark list"), "bookmark list");
|
||||
```
|
||||
|
||||
When you click `#delete-bookmark-button`, you should delete the bookmark corresponding to the selected radio button from the local storage and update the displayed bookmark list.
|
||||
|
||||
```js
|
||||
setLocalStorage();
|
||||
try {
|
||||
categoryDropdownTest.value = "news";
|
||||
viewCategoryButtonTest.dispatchEvent(new Event("click"));
|
||||
const firstRadioButton = document.querySelector('#category-list input[type="radio"]');
|
||||
firstRadioButton.checked = true;
|
||||
deleteBookmarkButtonTest.dispatchEvent(new Event("click"));
|
||||
let bookmarksDisplayed = document.querySelectorAll('#category-list input[type="radio"]');
|
||||
let bookmarkLabelsDisplayed = document.querySelectorAll('#category-list label');
|
||||
assert.lengthOf(bookmarksDisplayed, 1);
|
||||
assert.strictEqual(bookmarksDisplayed[0].id, "example4");
|
||||
assert.strictEqual(bookmarksDisplayed[0].value, "example4");
|
||||
assert.lengthOf(bookmarkLabelsDisplayed, 1);
|
||||
assert.strictEqual(bookmarkLabelsDisplayed[0].htmlFor, "example4");
|
||||
const expected = [{name: "example2", category: "entertainment", url: "example2.com"}, {name: "example3", category: "work", url: "example3.com"}, {name: "example4", category: "news", url: "example4.com"}];
|
||||
assert.deepEqual(getBookmarks(), expected);
|
||||
|
||||
} finally {
|
||||
resetLocalStorage();
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```js
|
||||
const viewCategoryButtonTest = document.getElementById("view-category-button");
|
||||
const closeListButtonTest = document.getElementById("close-list-button");
|
||||
const closeFormButtonTest = document.getElementById("close-form-button");
|
||||
const addBookMarkButtonTest = document.getElementById("add-bookmark-button");
|
||||
const addBookmarkButtonFormTest = document.getElementById("add-bookmark-button-form");
|
||||
const deleteBookmarkButtonTest = document.getElementById("delete-bookmark-button");
|
||||
const bookmarkNameTest = document.getElementById("name");
|
||||
const categoryDropdownTest = document.getElementById("category-dropdown");
|
||||
const bookmarkURLTest = document.getElementById("url");
|
||||
const mainSection = document.querySelector("#main-section");
|
||||
const formSection = document.querySelector("#form-section");
|
||||
const bookmarkListSection = document.querySelector("#bookmark-list-section");
|
||||
const getHidden = (notMain) => {
|
||||
if (notMain === "form") {
|
||||
if (mainSection.classList.contains("hidden") && !formSection.classList.contains("hidden")) {
|
||||
return "main"
|
||||
} else if (!mainSection.classList.contains("hidden") && formSection.classList.contains("hidden")) {
|
||||
return notMain;
|
||||
}
|
||||
} else if (notMain === "bookmark list") {
|
||||
if (mainSection.classList.contains("hidden") && !bookmarkListSection.classList.contains("hidden")) {
|
||||
return "main"
|
||||
} else if (!mainSection.classList.contains("hidden") && bookmarkListSection.classList.contains("hidden")) {
|
||||
return notMain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const originalLocalStorage = JSON.parse(localStorage.getItem("bookmarks")) || [];
|
||||
const temp = originalLocalStorage;
|
||||
function resetLocalStorage() {
|
||||
localStorage.setItem("bookmarks", JSON.stringify(temp));
|
||||
}
|
||||
|
||||
function setLocalStorage() {
|
||||
localStorage.setItem("bookmarks", JSON.stringify([{name: "example1", category: "news", url: "example1.com"}, {name: "example2", category: "entertainment", url: "example2.com"}, {name: "example3", category: "work", url: "example3.com"}, {name: "example4", category: "news", url: "example4.com"}]));
|
||||
}
|
||||
|
||||
function clearCategoryList() {
|
||||
const categoryList = document.getElementById("category-list");
|
||||
categoryList.innerHTML = "";
|
||||
}
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
|
||||
```
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.0">
|
||||
<title>Bookmark Manager</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<section id="main-section">
|
||||
<h1>Bookmark Manager</h1>
|
||||
<div id="dropdown">
|
||||
<label for="category-dropdown">Select a category:</label>
|
||||
<select id="category-dropdown" name="options">
|
||||
<option value="news" selected>News</option>
|
||||
<option value="entertainment">Entertainment</option>
|
||||
<option value="work">Work</option>
|
||||
<option value="miscellaneous">Miscellaneous</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="buttons">
|
||||
<button type="button" id="view-category-button">View Category</button>
|
||||
<button type="button" id="add-bookmark-button">Add Bookmark</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="form-section" class="hidden">
|
||||
<form>
|
||||
<h2 class="category-name"></h2>
|
||||
<div>
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name">
|
||||
</div>
|
||||
<div>
|
||||
<label for="url">URL:</label>
|
||||
<input type="text" id="url">
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" id="close-form-button">Go Back</button>
|
||||
<button type="button" id="add-bookmark-button-form">Add Bookmark</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section id="bookmark-list-section" class="hidden">
|
||||
<h2 class="category-name"></h2>
|
||||
<div id="category-list">
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" id="close-list-button">Go Back</button>
|
||||
<button type="button" id="delete-bookmark-button">Delete Bookmark</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
select,
|
||||
input,
|
||||
label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
div {
|
||||
padding: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.close-form-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#category-list {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
background-color: var(--light-grey);
|
||||
align-self: center;
|
||||
width: 80%;
|
||||
margin-top: 15px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#category-list,
|
||||
h1,
|
||||
h2,
|
||||
label {
|
||||
color: var(--light-grey);
|
||||
}
|
||||
|
||||
#category-list p {
|
||||
color: var(--dark-grey);
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
width: 100px;
|
||||
margin: 10px;
|
||||
color: var(--dark-grey);
|
||||
background-color: var(--golden-yellow);
|
||||
background-image: linear-gradient(#fecc4c, #ffac33);
|
||||
border-color: var(--golden-yellow);
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
section {
|
||||
margin-top: 60px;
|
||||
border: 2px solid var(--golden-yellow);
|
||||
width: fit-content;
|
||||
border-radius: 10px;
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
const categoryDropdown = document.getElementById("category-dropdown");
|
||||
const viewCategoryButton = document.getElementById("view-category-button");
|
||||
const addBookmarkButton = document.getElementById("add-bookmark-button");
|
||||
const mainSection = document.getElementById("main-section");
|
||||
const formSection = document.getElementById("form-section");
|
||||
const bookmarkListSection = document.getElementById("bookmark-list-section");
|
||||
const categoryHeading = document.querySelectorAll(".category-name");
|
||||
const closeFormButton = document.getElementById("close-form-button");
|
||||
const bookmarkName = document.getElementById("name");
|
||||
const bookmarkURL = document.getElementById("url");
|
||||
const addBookmarkButtonForm = document.getElementById("add-bookmark-button-form");
|
||||
const categoryList = document.getElementById("category-list");
|
||||
const closeListButton = document.getElementById("close-list-button");
|
||||
const deleteBookmarkButton = document.getElementById("delete-bookmark-button");
|
||||
const getBookmarks = () => JSON.parse(localStorage.getItem("bookmarks")) || [];
|
||||
|
||||
const displayOrCloseForm = () => {
|
||||
mainSection.classList.toggle("hidden");
|
||||
formSection.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
addBookmarkButton.addEventListener("click", () => {
|
||||
categoryHeading[0].innerText = categoryDropdown.value.charAt(0).toUpperCase()
|
||||
+ categoryDropdown.value.slice(1);
|
||||
displayOrCloseForm();
|
||||
});
|
||||
|
||||
closeFormButton.addEventListener("click", displayOrCloseForm);
|
||||
|
||||
const addBookmark = () => {
|
||||
const bookmarkObject = {
|
||||
name: bookmarkName.value,
|
||||
category: categoryDropdown.value,
|
||||
url: bookmarkURL.value
|
||||
};
|
||||
if (bookmarkName.value.length && bookmarkURL.value.length) {
|
||||
localStorage.setItem("bookmarks", JSON.stringify(getBookmarks().concat(bookmarkObject)))
|
||||
bookmarkName.value = "";
|
||||
bookmarkURL.value = "";
|
||||
displayOrCloseForm()
|
||||
} else {
|
||||
alert("Please, provide valid name and URL.")
|
||||
}
|
||||
}
|
||||
|
||||
addBookmarkButtonForm.addEventListener("click", addBookmark);
|
||||
|
||||
const displayOrHideCategory = () => {
|
||||
mainSection.classList.toggle("hidden");
|
||||
bookmarkListSection.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const fillBookmarkList = () => {
|
||||
categoryHeading[1].innerText = categoryDropdown.value.charAt(0).toUpperCase()
|
||||
+ categoryDropdown.value.slice(1);
|
||||
const bookmarksToDisplay = getBookmarks().filter((i) => {
|
||||
if (i.category === categoryDropdown.value) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
});
|
||||
if (bookmarksToDisplay.length) {
|
||||
categoryList.innerHTML = '';
|
||||
for (const bookmark of bookmarksToDisplay) {
|
||||
categoryList.innerHTML += `
|
||||
<div>
|
||||
<input type="radio" name="bookmarks" id="${bookmark.name}" value="${bookmark.name}">
|
||||
<label for="${bookmark.name}">
|
||||
<a href="${bookmark.url}">${bookmark.name}</a>
|
||||
</label>
|
||||
</div>`
|
||||
}
|
||||
} else {
|
||||
categoryList.innerHTML = '<p>No Bookmarks Found</p>';
|
||||
}
|
||||
}
|
||||
|
||||
viewCategoryButton.addEventListener("click", () => {
|
||||
fillBookmarkList();
|
||||
displayOrHideCategory();
|
||||
})
|
||||
|
||||
closeListButton.addEventListener("click", displayOrHideCategory)
|
||||
|
||||
const deleteBookmark = () => {
|
||||
const radioBookmarks = document.querySelectorAll('input[type="radio"]');
|
||||
for (const radioBookmark of radioBookmarks) {
|
||||
if (radioBookmark.checked) {
|
||||
const bookmarks = getBookmarks();
|
||||
const indexToRemove = bookmarks.findIndex(i => i.name == radioBookmark.value);
|
||||
bookmarks.splice(indexToRemove, 1);
|
||||
localStorage.setItem("bookmarks", JSON.stringify(bookmarks));
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteBookmarkButton.addEventListener("click", () => {
|
||||
deleteBookmark();
|
||||
fillBookmarkList();
|
||||
})
|
||||
```
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.0">
|
||||
<title>Bookmark Manager</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<section id="main-section">
|
||||
<h1>Bookmark Manager</h1>
|
||||
<div id="dropdown">
|
||||
<label for="category-dropdown">Select a category:</label>
|
||||
<select id="category-dropdown" name="options">
|
||||
<option value="news" selected>News</option>
|
||||
<option value="entertainment">Entertainment</option>
|
||||
<option value="work">Work</option>
|
||||
<option value="miscellaneous">Miscellaneous</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="buttons">
|
||||
<button type="button" id="view-category-button">View Category</button>
|
||||
<button type="button" id="add-bookmark-button">Add Bookmark</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="form-section" class="hidden">
|
||||
<form>
|
||||
<h2 class="category-name"></h2>
|
||||
<div>
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name">
|
||||
</div>
|
||||
<div>
|
||||
<label for="url">URL:</label>
|
||||
<input type="text" id="url">
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" id="close-form-button">Go Back</button>
|
||||
<button type="button" id="add-bookmark-button-form">Add Bookmark</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section id="bookmark-list-section" class="hidden">
|
||||
<h2 class="category-name"></h2>
|
||||
<div id="category-list">
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" id="close-list-button">Go Back</button>
|
||||
<button type="button" id="delete-bookmark-button">Delete Bookmark</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
select,
|
||||
input,
|
||||
label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
div {
|
||||
padding: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.close-form-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#category-list {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
background-color: var(--light-grey);
|
||||
align-self: center;
|
||||
width: 80%;
|
||||
margin-top: 15px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#category-list,
|
||||
h1,
|
||||
h2,
|
||||
label {
|
||||
color: var(--light-grey);
|
||||
}
|
||||
|
||||
#category-list p {
|
||||
color: var(--dark-grey);
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
width: 100px;
|
||||
margin: 10px;
|
||||
color: var(--dark-grey);
|
||||
background-color: var(--golden-yellow);
|
||||
background-image: linear-gradient(#fecc4c, #ffac33);
|
||||
border-color: var(--golden-yellow);
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
section {
|
||||
margin-top: 60px;
|
||||
border: 2px solid var(--golden-yellow);
|
||||
width: fit-content;
|
||||
border-radius: 10px;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user