mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-09-24 23:01:18 +08:00
chore(i18n,learn): processed translations (#51907)
This commit is contained in:
+18
-1
@@ -54,7 +54,24 @@ assert.match(code, /myStorage\.car\.inside/);
|
||||
`gloveBoxContents` should still be declared with `const`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+gloveBoxContents\s*=\s*myStorage\.car\.inside\[\s*("|')glove box\1\s*\]|const\s*{\s*('|")glove box\2:\s*gloveBoxContents\s*}\s*=\s*myStorage\.car\.inside;/);
|
||||
assert.match(code, /const\s+gloveBoxContents\s*=/);
|
||||
```
|
||||
|
||||
You should not change the `myStorage` object.
|
||||
|
||||
```js
|
||||
const expectedMyStorage = {
|
||||
"car":{
|
||||
"inside":{
|
||||
"glove box":"maps",
|
||||
"passenger seat":"crumbs"
|
||||
},
|
||||
"outside":{
|
||||
"trunk":"jack"
|
||||
}
|
||||
}
|
||||
};
|
||||
assert.deepStrictEqual(myStorage, expectedMyStorage);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ dashedName: catch-misspelled-variable-and-function-names
|
||||
|
||||
يكونا `console.log()` و `typeof` الطرقتان الرئيستان للتحقق من القيم وأنواعها في الكود. الآن حان الوقت للدخول في الأشكال الشائعة التي تأخذها الأخطاء (bugs). أحد المشاكل ويقع فيه بالذات الكتبة السريعين هو الخطأ الإملائي المتواضع (humble spelling error).
|
||||
|
||||
الأحرف المبدلة، المفقودة، أو الأحرف كبيرة بالخطأ (mis-capitalized) في اسم متغير أو وظيفة سيجعل المتصفح يبحث عن شيء غير موجود - ويبلغ عن خطأ مرجعي (reference error). في لغة JavaScript أسماء المتغير والوظائف هي حساسة لحالة الأحرف (case-sensitive).
|
||||
Transposed, missing, or miscapitalized characters in a variable or function name will have the browser looking for an object that doesn't exist - and complain in the form of a reference error. في لغة JavaScript أسماء المتغير والوظائف هي حساسة لحالة الأحرف (case-sensitive).
|
||||
|
||||
# --instructions--
|
||||
|
||||
|
||||
+2
-2
@@ -58,8 +58,8 @@ class PrimeSeive {
|
||||
// Mark value in seive array
|
||||
const prime = 2 * i + 3;
|
||||
// Mark all multiples of this number as false (not prime)
|
||||
const primeSqaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSqaredIndex; j < upper; j += prime) {
|
||||
const primeSquaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSquaredIndex; j < upper; j += prime) {
|
||||
seive[j] = false;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -231,13 +231,13 @@ async (getUserInput) => {
|
||||
};
|
||||
const url = getUserInput('url') + '/api/issues/fcc-project';
|
||||
const itemToUpdate = await $.post(url, initialData);
|
||||
const updateSucccess = await $.ajax({
|
||||
const updateSuccess = await $.ajax({
|
||||
url: url,
|
||||
type: 'PUT',
|
||||
data: { _id: itemToUpdate._id, issue_text: 'New Issue Text' }
|
||||
});
|
||||
assert.isObject(updateSucccess);
|
||||
assert.deepEqual(updateSucccess, {
|
||||
assert.isObject(updateSuccess);
|
||||
assert.deepEqual(updateSuccess, {
|
||||
result: 'successfully updated',
|
||||
_id: itemToUpdate._id
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ const OPERATORS_ = {
|
||||
"/": (a, b) => a / b,
|
||||
}
|
||||
|
||||
const PRECIDENCE_ = {
|
||||
const PRECEDENCE_ = {
|
||||
"+": 1,
|
||||
"-": 1,
|
||||
"*": 2,
|
||||
@@ -114,7 +114,7 @@ function evaluate_(expression) {
|
||||
case "*":
|
||||
case "/":
|
||||
while (stack.length &&
|
||||
PRECIDENCE_[c] <= PRECIDENCE_[stack[stack.length-1]]) {
|
||||
PRECEDENCE_[c] <= PRECEDENCE_[stack[stack.length-1]]) {
|
||||
postfix += stack.pop();
|
||||
}
|
||||
stack.push(c);
|
||||
|
||||
+3
-3
@@ -175,7 +175,7 @@ function dealFreeCell(seed) {
|
||||
const rng = FreeCellRNG(seed);
|
||||
const deck = getDeck();
|
||||
|
||||
const deltCards = [[], [], [], [], [], [], []];
|
||||
const dealtCards = [[], [], [], [], [], [], []];
|
||||
let currentColumn = 0;
|
||||
let currentRow = 0;
|
||||
|
||||
@@ -195,7 +195,7 @@ function dealFreeCell(seed) {
|
||||
card = deck.pop();
|
||||
|
||||
// Deal this card
|
||||
deltCards[currentRow].push(card);
|
||||
dealtCards[currentRow].push(card);
|
||||
currentColumn += 1;
|
||||
if (currentColumn === 8) {
|
||||
currentColumn = 0;
|
||||
@@ -203,6 +203,6 @@ function dealFreeCell(seed) {
|
||||
}
|
||||
}
|
||||
|
||||
return deltCards;
|
||||
return dealtCards;
|
||||
}
|
||||
```
|
||||
|
||||
+4
-4
@@ -101,7 +101,7 @@ function combinations(possibleNumbers, total) {
|
||||
function combinations(possibleNumbers, total) {
|
||||
let firstNumber;
|
||||
let secondNumber;
|
||||
let thridNumber;
|
||||
let thirdNumber;
|
||||
const allCombinations = [];
|
||||
|
||||
for (let i = 0; i < possibleNumbers.length; i += 1) {
|
||||
@@ -112,10 +112,10 @@ function combinations(possibleNumbers, total) {
|
||||
secondNumber = possibleNumbers[j];
|
||||
|
||||
if (j !== i && firstNumber + secondNumber <= total) {
|
||||
thridNumber = total - firstNumber - secondNumber;
|
||||
thirdNumber = total - firstNumber - secondNumber;
|
||||
|
||||
if (thridNumber !== firstNumber && thridNumber !== secondNumber && possibleNumbers.includes(thridNumber)) {
|
||||
allCombinations.push([firstNumber, secondNumber, thridNumber]);
|
||||
if (thirdNumber !== firstNumber && thirdNumber !== secondNumber && possibleNumbers.includes(thirdNumber)) {
|
||||
allCombinations.push([firstNumber, secondNumber, thirdNumber]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ $PA = LU$
|
||||
|
||||
# --instructions--
|
||||
|
||||
The task is to implement a routine which will take a square nxn matrix $A$ and return a lower triangular matrix $L$, a upper triangular matrix $U$ and a permutation matrix $P$, so that the above equation is fullfilled. The returned value should be in the form `[L, U, P]`.
|
||||
The task is to implement a routine which will take a square nxn matrix $A$ and return a lower triangular matrix $L$, a upper triangular matrix $U$ and a permutation matrix $P$, so that the above equation is fulfilled. The returned value should be in the form `[L, U, P]`.
|
||||
|
||||
# --hints--
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ dashedName: step-82
|
||||
|
||||
يشترك منقار البِطْرِيق وقدميه في نفس اللون `color`.
|
||||
|
||||
أنشئ متغير CSS مخصص جديد يسمى `--penguin-picorna`، واستبدل جميع قيم الخاصية ذات صلة بهما.
|
||||
Create a new custom CSS variable named `--penguin-picorna`, and replace all relevant property values with it.
|
||||
|
||||
# --hints--
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ Inside `pick`, use `let` to initialize a variable named `numbers` and set it to
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `pick` function should initalize `numbers` to an empty array `[]`.
|
||||
Your `pick` function should initialize `numbers` to an empty array `[]`.
|
||||
|
||||
```js
|
||||
assert.match(pick.toString(), /numbers\s*=\s*\[\]/);
|
||||
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
---
|
||||
id: 64e4e4c4ec263b62ae7bf54d
|
||||
title: Step 1
|
||||
challengeType: 0
|
||||
dashedName: step-1
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this project, you will learn how `localStorage` works in JavaScript by building a Todo app. <dfn>LocalStorage</dfn> is a web storage feature of JavaScript that lets you persist data by storing the data as a **key:value** pair.
|
||||
|
||||
The HTML and CSS for this project have been provided for you. Take a look at the files to get yourself familiarized with them.
|
||||
|
||||
Begin by accessing the `task-form`, `confirm-close-dialog`, and `open-task-form-btn` elements with the `getElementById()` method. Save them in the variables `taskForm`, `confirmCloseDialog`, and `openTaskFormBtn`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `getElementById()` to access the `task-form` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)task\-form\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `task-form` element to the variable `taskForm`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskForm\s*=\s*document\.getElementById\(\s*('|"|`)task\-form\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `confirm-close-dialog` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)confirm\-close\-dialog\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `confirm-close-dialog` element to the variable `confirmCloseDialog`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+confirmCloseDialog\s*=\s*document\.getElementById\(\s*('|"|`)confirm\-close\-dialog\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `open-task-form-btn` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)open\-task\-form\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `open-task-form-btn` element to the variable `openTaskFormBtn`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+openTaskFormBtn\s*=\s*document\.getElementById\(\s*('|"|`)open\-task\-form\-btn\1\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
---
|
||||
id: 64e4e6c86954de67a3e44ee3
|
||||
title: Step 2
|
||||
challengeType: 0
|
||||
dashedName: step-2
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You need to access more elements with the `getElementById()` method. This time you need the `close-task-form-btn`, `add-or-update-task-btn`, and `cancel-btn` elements. Save them in the variables `closeTaskFormBtn`, `addOrUpdateTaskBtn`, and `cancelBtn`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `getElementById()` to access the `close-task-form-btn` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)close\-task\-form\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `close-task-form-btn` element to the variable `closeTaskFormBtn`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+closeTaskFormBtn\s*=\s*document\.getElementById\(\s*('|"|`)close\-task\-form\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `add-or-update-task-btn` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)add\-or\-update\-task\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `add-or-update-task-btn` element to the variable `addOrUpdateTaskBtn`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+addOrUpdateTaskBtn\s*=\s*document\.getElementById\(\s*('|"|`)add\-or\-update\-task\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `cancel-btn` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)cancel\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `cancel-btn` element to the variable `cancelBtn`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+cancelBtn\s*=\s*document\.getElementById\(\s*('|"|`)cancel\-btn\1\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
---
|
||||
id: 64e4e6fe78b5aa67ef2fc3e7
|
||||
title: Step 3
|
||||
challengeType: 0
|
||||
dashedName: step-3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Next, access the `discard-btn`, `tasks-container`, and `title-input` elements using the `getElementById()` method. Save them in variables named `discardBtn`, `tasksContainer`, and `titleInput`, respectively.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `getElementById()` to access the `discard-btn` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)discard\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `discard-btn` element to the variable `discardBtn`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+discardBtn\s*=\s*document\.getElementById\(\s*('|"|`)discard\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `tasks-container` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)tasks\-container\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `tasks-container` element to the variable `tasksContainer`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+tasksContainer\s*=\s*document\.getElementById\(\s*('|"|`)tasks\-container\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `title-input` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)title\-input\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `title-input` element to the variable `titleInput`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+titleInput\s*=\s*document\.getElementById\(\s*('|"|`)title\-input\1\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
---
|
||||
id: 64e4e7241f52bb682eeb8211
|
||||
title: Step 4
|
||||
challengeType: 0
|
||||
dashedName: step-4
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The last set of elements you need to get from the HTML file are the `date-input` and `description-input` elements. Save them in the variables `dateInput` and `descriptionInput` respectively.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `getElementById()` to access the `date-input` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)date\-input\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `date-input` element to the variable `dateInput`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+dateInput\s*=\s*document\.getElementById\(\s*('|"|`)date\-input\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `description-input` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)description\-input\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `description-input` element to the variable `descriptionInput`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+descriptionInput\s*=\s*document\.getElementById\(\s*('|"|`)description\-input\1\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
---
|
||||
id: 64e4e74d0fb4f0687bf4145d
|
||||
title: Step 5
|
||||
challengeType: 0
|
||||
dashedName: step-5
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Create a `taskData` constant and set it to an empty array. This array will store all the tasks along with their associated data, including title, due date, and description. This storage will enable you to keep track of tasks, display them on the page, and save them to `localStorage`.
|
||||
|
||||
Use `let` to create a `currentTask` variable and set it to an empty object. This variable will be used to track the state when editing and discarding tasks.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a `taskData` constant set to an empty array.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskData\s*=\s*\[\];?/)
|
||||
```
|
||||
|
||||
You should use `let` to create an empty `currentTask` object.
|
||||
|
||||
```js
|
||||
assert.match(code, /let\s+currentTask\s*=\s*\{\};?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
---
|
||||
id: 64e4e78a7ea4a168de4e6a38
|
||||
title: Step 6
|
||||
challengeType: 0
|
||||
dashedName: step-6
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now, you will work on opening and closing the form modal.
|
||||
|
||||
Add a `click` event listener to the `openTaskFormBtn` variable, and then use `classList` to toggle the `hidden` class on the `taskForm` element.
|
||||
|
||||
Now you can click on the "Add new Task" button and see the form modal.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should call the `addEventListener()` method on your `openTaskFormBtn` variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /openTaskFormBtn\.addEventListener\(/)
|
||||
```
|
||||
|
||||
Your event listener should listen for a `click` event.
|
||||
|
||||
```js
|
||||
assert.match(code, /openTaskFormBtn\.addEventListener\(('|"|`)click\1/)
|
||||
```
|
||||
|
||||
Your event listener should use arrow syntax, then use `classList` property and it's `toggle` method on the `taskForm`, to toggle the class `hidden`. Don't use curly braces.
|
||||
|
||||
```js
|
||||
assert.match(code, /openTaskFormBtn\.addEventListener\(('|"|`)click\1\,\s*\(\)\s*\s*=>\s*taskForm\.classList\.toggle\(\1hidden\1\)\s*\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
---
|
||||
id: 64e4e7bbedb22d6939001ad3
|
||||
title: Step 7
|
||||
challengeType: 0
|
||||
dashedName: step-7
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Add a `click` event listener to the `closeTaskFormBtn` variable, then use the `showModal()` method on your `confirmCloseDialog` variable. This will display a modal with the `Discard` and `Cancel` buttons.
|
||||
|
||||
`showModal()` is a method associated with the HTML `dialog` element. It is used to display a modal dialog box on a web page.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should call the `addEventListener()` method on your `closeTaskFormBtn` variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /closeTaskFormBtn\.addEventListener\(/)
|
||||
```
|
||||
|
||||
Your event listener should listen for a `click` event.
|
||||
|
||||
```js
|
||||
assert.match(code, /closeTaskFormBtn\.addEventListener\(('|"|`)click\1/)
|
||||
```
|
||||
|
||||
Your event listener should use arrow syntax to set the `showModal()` method on `confirmCloseDialog`.
|
||||
|
||||
```js
|
||||
assert.match(code, /closeTaskFormBtn\.addEventListener\(["'`]click["'`],\s*\(.*\)\s*=>\s*{?\s*confirmCloseDialog\.showModal\(\);?\s*}?\s*\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
---
|
||||
id: 64e4eaaa9070a66aecbfe603
|
||||
title: Step 8
|
||||
challengeType: 0
|
||||
dashedName: step-8
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
If the user clicks the `Cancel` button, you want to cancel the process (close the modal showing the two buttons) so the user can continue editing.
|
||||
|
||||
Add a `click` event listener to the `cancelBtn` element, then use the `close()` method on the `confirmCloseDialog` variable.
|
||||
|
||||
`close()` is a method of the window object you can use to close the current window, or a modal you create with the `dialog` element.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should call the `addEventListener()` method on your `cancelBtn` variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /cancelBtn\.addEventListener\(/)
|
||||
```
|
||||
|
||||
Your event listener should listen for a `click` event.
|
||||
|
||||
```js
|
||||
assert.match(code, /cancelBtn\.addEventListener\(('|"|`)click\1/)
|
||||
```
|
||||
|
||||
Your event listener should use arrow syntax to set the `close()` method on `confirmCloseDialog`. Don't use curly braces.
|
||||
|
||||
```js
|
||||
assert.match(code, /cancelBtn\.addEventListener\(('|"|`)click\1\,\s*\(\)\s*\s*=>\s*confirmCloseDialog\.close\(\)\s*\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
```
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
---
|
||||
id: 64e4ebc7eabc5a6babd479cd
|
||||
title: Step 9
|
||||
challengeType: 0
|
||||
dashedName: step-9
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
If the user clicks the `Discard` button, you want to close the modal showing the `Cancel` and `Discard` buttons, then hide the form modal.
|
||||
|
||||
Add a click event listener to `discardBtn`, then use the `close()` method on the `confirmCloseDialog` variable. Also, use `classList` to toggle the class `hidden` on `taskForm` so the form modal will close too.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should call the `addEventListener()` method on your `discardBtn` variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /discardBtn\.addEventListener\(/)
|
||||
```
|
||||
|
||||
Your event listener should listen for a `click` event.
|
||||
|
||||
```js
|
||||
assert.match(code, /discardBtn\.addEventListener\(('|"|`)click\1/)
|
||||
```
|
||||
|
||||
You should use arrow syntax to set your event listener to an empty pair of curly braces.
|
||||
|
||||
```js
|
||||
assert.match(code, /discardBtn\.addEventListener\(('|"|`)click\1,\s*\(\)\s*=>\s*\{/)
|
||||
```
|
||||
|
||||
Your event listener should use the `close()` method on `confirmCloseDialog`.
|
||||
|
||||
```js
|
||||
assert.match(code, /discardBtn\.addEventListener\(('|"|`)click\1,\s*\(\)\s*=>\s*\{\s*confirmCloseDialog\.close\(\);?/)
|
||||
```
|
||||
|
||||
Your event listener should use `classList` to toggle the class `hidden` on `taskForm`.
|
||||
|
||||
```js
|
||||
assert.match(code, /discardBtn\.addEventListener\(('|"|`)click\1,\s*\(\)\s*=>\s*\{\s*confirmCloseDialog\.close\(\);?\s*taskForm\.classList\.toggle\(\1hidden\1\);?\s*\}\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
---
|
||||
id: 64e4ecd7735a566c9266a338
|
||||
title: Step 10
|
||||
challengeType: 0
|
||||
dashedName: step-10
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now that you've worked on opening and closing the modal, it's time to get the values from the input fields, save them into the `taskData` array, and display them on the page.
|
||||
|
||||
To start, add a `submit` event listener to your `taskForm` element and pass in `e` as the parameter of your arrow function. Inside the curly braces, use the `preventDefault()` method to stop the browser from refreshing the page after submitting the form.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should call the `addEventListener()` method on your `taskForm` variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskForm\.addEventListener\(/)
|
||||
```
|
||||
|
||||
Your event listener should listen for a `submit` event.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskForm\.addEventListener\(('|"|`)submit\1/)
|
||||
```
|
||||
|
||||
You should use arrow syntax to set your event listener to an empty pair of curly braces with `e` as the parameter.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskForm\.addEventListener\(('|"|`)submit\1,\s*\(e\)\s*=>\s*\{/)
|
||||
```
|
||||
|
||||
You should use the `e.preventDefault()` method to stop the browser from reloading the page.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskForm\.addEventListener\(('|"|`)submit\1,\s*\(e\)\s*=>\s*\{\s*e\.preventDefault\(\);?\s*\}\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+337
@@ -0,0 +1,337 @@
|
||||
---
|
||||
id: 64e4eec13546c06d61a63d59
|
||||
title: Step 11
|
||||
challengeType: 0
|
||||
dashedName: step-11
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You need to determine whether the task being added already exists or not. If it doesn't exist, you will add it, and if it already exists, you will set it up for editing. You can use the `findIndex()` method to accomplish this.
|
||||
|
||||
`findIndex` is an array method that lets find the index of the first element in an array that satisfies a given testing function.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```js
|
||||
const numbers = [3, 1, 5, 6, 10, 9, 8];
|
||||
const firstEvenNumIndex = numbers.findIndex((num) => num % 2 === 0);
|
||||
|
||||
console.log(firstEvenNumIndex); // Output: 3 – because the first even number (6) is at index 3
|
||||
```
|
||||
|
||||
Declare a `dataArrIndex` variable using `const` and set it to the result of the `findIndex()` method applied to the `taskData` array. Utilize arrow syntax to provide a callback function with `item` as the parameter, and within the callback, check if the `id` property of `item` is equal to the `id` property of `currentTask`.
|
||||
|
||||
If the task exists, this returns the index, and if it doesn't exist, it returns `-1`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` to declare a `dataArrIndex` variable and set it to `taskData.findIndex()`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const dataArrIndex\s*=\s*taskData\.findIndex\(/)
|
||||
```
|
||||
|
||||
You should pass in `item` as the parameter of the arrow function callback. Don't use curly braces.
|
||||
|
||||
```js
|
||||
assert.match(code, /const dataArrIndex\s*=\s*taskData\.findIndex\(\(?item\)?/)
|
||||
```
|
||||
|
||||
Your arrow function callback should check if `item.id === currentTask.id`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const dataArrIndex\s*=\s*taskData\.findIndex\(\(?item\)?\s*=>\s*item.id\s*===\s*currentTask.id\s*\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
});
|
||||
```
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
---
|
||||
id: 64e4f01d6c72086e016a8626
|
||||
title: Step 12
|
||||
challengeType: 0
|
||||
dashedName: step-12
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Next, retrieve the values from the input fields and store them in a `taskObj` object. Each task should also have a unique `id`.
|
||||
|
||||
Create a `taskObj` object with an `id` property as the first property. For the value of the `id` property, retrieve the value of the `titleInput` field, convert it to lowercase, and then use the `split()` and `join()` methods to hyphenate it.
|
||||
|
||||
Make sure all of those are in template literals because you need the `id` property value as a string.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` to create a `taskObj` object.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskObj\s*=\s*\{\s*/)
|
||||
```
|
||||
|
||||
Your `taskObj` object should have a key of `id`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskObj\s*=\s*\{\s*id/)
|
||||
```
|
||||
|
||||
You should use template strings to get `titleInput.value` as the value of your `id` key and convert it to lowercase.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskObj\s*=\s*\{\s*id:\s*`\$\{titleInput\.value\.toLowerCase\(\)/)
|
||||
```
|
||||
|
||||
You should use `.split(' ')` on `titleInput.value.toLowerCase()`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskObj\s*=\s*\{\s*id:\s*`\$\{titleInput\.value\.toLowerCase\(\)\.split\(('|")\s{1}\1\)/)
|
||||
```
|
||||
|
||||
You should use `.join('-')` on `titleInput.value.toLowerCase().split(' ')`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskObj\s*=\s*\{\s*id:\s*`\$\{titleInput\.value\.toLowerCase\(\)\.split\(('|")\s{1}\1\)\.join\(\1-\1\)\}`,?\s*\}/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
});
|
||||
```
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
---
|
||||
id: 64ec89ee549ecf802de2b3e2
|
||||
title: Step 14
|
||||
challengeType: 0
|
||||
dashedName: step-14
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Retrieve the values from the `titleInput`, `dateInput`, and `descriptionInput` fields, and then save them in the properties `title`, `date`, and `description` of the `taskObj` object.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should get the value of `titleInput` and save it in a `title` key.
|
||||
|
||||
```js
|
||||
assert.match(code, /title:\s*titleInput\.value,/)
|
||||
```
|
||||
|
||||
You should get the value of `dateInput` and save it in a `date` key.
|
||||
|
||||
```js
|
||||
assert.match(code, /date:\s*dateInput\.value,/)
|
||||
```
|
||||
|
||||
You should get the value of `descriptionInput` and save it in a `description` key.
|
||||
|
||||
```js
|
||||
assert.match(code, /description:\s*descriptionInput\.value,?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
};
|
||||
});
|
||||
```
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
---
|
||||
id: 64ec8f717b261e824d82d6a5
|
||||
title: Step 15
|
||||
challengeType: 0
|
||||
dashedName: step-15
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now that you have obtained the values from the input fields and generated an `id`, you want to add them to your `taskData` array to keep track of each task. However, you should only do this if the task is new. If the task already exists, you will set it up for editing. This is why you have the `dataArrIndex` variable, which provides the index of each task.
|
||||
|
||||
Create an `if` statement with the condition `dataArrIndex === -1`. Within the `if` statement, use the `unshift()` method to add the `taskObj` object to the beginning of the `taskData` array.
|
||||
|
||||
`unshift()` is an array method that is used to add one or more elements to the beginning of an array.
|
||||
|
||||
**N.B:** Try adding a task and log `taskData` to the console to see what it looks like.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create an `if` statement with the condition `dataArrIndex === -1`.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(dataArrIndex\s*===\s*-1\)\s*\{/)
|
||||
```
|
||||
|
||||
Your `if` statement should have `taskData.unshift(taskObj)` in it's body.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(dataArrIndex\s*===\s*-1\)\s*\{\s*taskData\.unshift\(taskObj\);?\s*\}/)
|
||||
```
|
||||
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
});
|
||||
```
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
---
|
||||
id: 64ec9145e424d8835a4e0f28
|
||||
title: Step 16
|
||||
challengeType: 0
|
||||
dashedName: step-16
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now that you have saved the task in the `taskData` array, you should display the task on the page by looping through it.
|
||||
|
||||
Use `forEach()` on `taskData`, then destructure `id`, `title`, `date`, `description` as the parameters. Don't return anything yet.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `forEach()` on `taskData`.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskData\.forEach\(\s*/)
|
||||
```
|
||||
|
||||
You should destructure `id`, `title`, `date`, `description` as the parameters of the callback of your `forEach()`.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskData\.forEach\(\s*\(\s*\{\s*(?:id\s*,\s*title\s*,\s*date\s*,\s*description)|(?:title\s*,\s*id\s*,\s*date\s*,\s*description)|(?:date\s*,\s*id\s*,\s*title\s*,\s*description)|(?:id\s*,\s*date\s*,\s*title\s*,\s*description)|(?:title\s*,\s*date\s*,\s*id\s*,\s*description)|(?:date\s*,\s*title\s*,\s*id\s*,\s*description)|(?:date\s*,\s*title\s*,\s*description\s*,\s*id)|(?:title\s*,\s*date\s*,\s*description\s*,\s*id)|(?:description\s*,\s*date\s*,\s*title\s*,\s*id)|(?:date\s*,\s*description\s*,\s*title\s*,\s*id)|(?:title\s*,\s*description\s*,\s*date\s*,\s*id)|(?:description\s*,\s*title\s*,\s*date\s*,\s*id)|(?:description\s*,\s*id\s*,\s*date\s*,\s*title)|(?:id\s*,\s*description\s*,\s*date\s*,\s*title)|(?:date\s*,\s*description\s*,\s*id\s*,\s*title)|(?:description\s*,\s*date\s*,\s*id\s*,\s*title)|(?:id\s*,\s*date\s*,\s*description\s*,\s*title)|(?:date\s*,\s*id\s*,\s*description\s*,\s*title)|(?:title\s*,\s*id\s*,\s*description\s*,\s*date)|(?:id\s*,\s*title\s*,\s*description\s*,\s*date)|(?:description\s*,\s*title\s*,\s*id\s*,\s*date)|(?:title\s*,\s*description\s*,\s*id\s*,\s*date)|(?:id\s*,\s*description\s*,\s*title\s*,\s*date)|(?:description\s*,\s*id\s*,\s*title\s*,\s*date)\s*\}/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
});
|
||||
```
|
||||
+336
@@ -0,0 +1,336 @@
|
||||
---
|
||||
id: 64ec9282cd547785258cecf2
|
||||
title: Step 17
|
||||
challengeType: 0
|
||||
dashedName: step-17
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Using arrow syntax, create an implicit return and use addition assignment to set the `innerHTML` of `tasksContainer` to empty backticks.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should not alter the existing `taskData.forEach()` and the values you destructured.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskData\.forEach\(\s*\(\s*\{\s*(?:id\s*,\s*title\s*,\s*date\s*,\s*description)|(?:title\s*,\s*id\s*,\s*date\s*,\s*description)|(?:date\s*,\s*id\s*,\s*title\s*,\s*description)|(?:id\s*,\s*date\s*,\s*title\s*,\s*description)|(?:title\s*,\s*date\s*,\s*id\s*,\s*description)|(?:date\s*,\s*title\s*,\s*id\s*,\s*description)|(?:date\s*,\s*title\s*,\s*description\s*,\s*id)|(?:title\s*,\s*date\s*,\s*description\s*,\s*id)|(?:description\s*,\s*date\s*,\s*title\s*,\s*id)|(?:date\s*,\s*description\s*,\s*title\s*,\s*id)|(?:title\s*,\s*description\s*,\s*date\s*,\s*id)|(?:description\s*,\s*title\s*,\s*date\s*,\s*id)|(?:description\s*,\s*id\s*,\s*date\s*,\s*title)|(?:id\s*,\s*description\s*,\s*date\s*,\s*title)|(?:date\s*,\s*description\s*,\s*id\s*,\s*title)|(?:description\s*,\s*date\s*,\s*id\s*,\s*title)|(?:id\s*,\s*date\s*,\s*description\s*,\s*title)|(?:date\s*,\s*id\s*,\s*description\s*,\s*title)|(?:title\s*,\s*id\s*,\s*description\s*,\s*date)|(?:id\s*,\s*title\s*,\s*description\s*,\s*date)|(?:description\s*,\s*title\s*,\s*id\s*,\s*date)|(?:title\s*,\s*description\s*,\s*id\s*,\s*date)|(?:id\s*,\s*description\s*,\s*title\s*,\s*date)|(?:description\s*,\s*id\s*,\s*title\s*,\s*date)\s*\}/)
|
||||
```
|
||||
|
||||
You should use arrow syntax and attach `innerHTML` to `tasksContainer`.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskData\.forEach\(\(\{.*\}\)\s*=>\s*(\(\s*tasksContainer\.innerHTML\s*\))?/)
|
||||
```
|
||||
|
||||
You should use addition assignment to set the `innerHTML` of `tasksContainer` to an empty pair of backticks.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskData\.forEach\(\(\{.*\}\)\s*=>\s*(\s*\(?tasksContainer\.innerHTML\s*\+=\s*`\s*`\)?)/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
--fcc-editable-region--
|
||||
taskData.forEach(({id, title, date, description}));
|
||||
|
||||
--fcc-editable-region--
|
||||
});
|
||||
```
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
---
|
||||
id: 64ec9343769e8f85c1e17e05
|
||||
title: Step 18
|
||||
challengeType: 0
|
||||
dashedName: step-18
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Create a `div` element with the class of `task`. Utilize template strings to set the `id` attribute of the `div` to the `id` you destructured from the task data.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a `div` element with the class `task`.
|
||||
|
||||
```js
|
||||
assert.match(code, /<div\s+class=('|")task\1/)
|
||||
```
|
||||
|
||||
Your `div` element should have the `id` `${id}`.
|
||||
|
||||
```js
|
||||
assert.match(code, /<div\s+class=('|")task\1\s*id=\1\$\{id\}\1>\s*<\/div>/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(({id, title, date, description}) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
`)
|
||||
);
|
||||
});
|
||||
```
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
---
|
||||
id: 64ec94f0de20c086e09b0fc3
|
||||
title: Step 19
|
||||
challengeType: 0
|
||||
dashedName: step-19
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Create a `p` element and use template strings to set its content to the `title` you destructured. Right before the content of the `p` element, create a `strong` element with the text `Title:`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a `p` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /<p>/)
|
||||
```
|
||||
|
||||
You should interpolate `${title}` as the text of your `p` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /<p>.*\$\{title\}<\/p>/)
|
||||
```
|
||||
|
||||
You should create a `strong` element after the opening tag of your `p` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /(?<=<p>)<strong>/)
|
||||
```
|
||||
|
||||
Your `strong` element should have the text `Title:`.
|
||||
|
||||
```js
|
||||
assert.match(code, /(?<=<p>)<strong>Title:\s*<\/strong>\s*/)
|
||||
```
|
||||
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(({id, title, date, description}) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
});
|
||||
```
|
||||
+336
@@ -0,0 +1,336 @@
|
||||
---
|
||||
id: 64ec959a76336c8767f5cd4d
|
||||
title: Step 20
|
||||
challengeType: 0
|
||||
dashedName: step-20
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Similarly to the previous step, create another `p` element, and interpolate the `date` you destructured as the text content. Inside this paragraph, create a `strong` element with the text `Date:`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a `p` element and interpolate `${date}` as the text.
|
||||
|
||||
```js
|
||||
assert.match(code, /<p>.*\$\{date\}<\/p>/)
|
||||
```
|
||||
|
||||
You should create a `strong` element with the text `Date:` after the opening tag of your `p` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /(?<=<p>)<strong>Date:\s*<\/strong>\s*/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(({id, title, date, description}) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
});
|
||||
```
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
---
|
||||
id: 64ec96761156a187ed32b274
|
||||
title: Step 22
|
||||
challengeType: 0
|
||||
dashedName: step-22
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
To allow for task management, you need to include both a delete and an edit button for each task.
|
||||
|
||||
Create two `button` elements with the `type` attribute set to `button` and the `class` attribute set to `btn`. Set the text of the first button to `Edit` and the text of the second button to `Delete`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a `button` element of type `button`, a class `btn` and `Edit` as the text, in that order.
|
||||
|
||||
```js
|
||||
assert.match(code, /<button\s+type=('|")button\1\s+class=('|")btn\2>Edit<\/button/)
|
||||
```
|
||||
|
||||
You should create a `button` element of type `button` a class `btn` and `Delete` as the text, in that order.
|
||||
|
||||
```js
|
||||
assert.match(code, /<button\s+type=('|")button\1\s+class=('|")btn\2>Delete<\/button/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(({id, title, date, description}) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
});
|
||||
```
|
||||
+336
@@ -0,0 +1,336 @@
|
||||
---
|
||||
id: 64ec9b10356c2d8aa05d9ce1
|
||||
title: Step 23
|
||||
challengeType: 0
|
||||
dashedName: step-23
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
After adding the task to the page, you should close the form modal to view the task. To do this, utilize `classList` to toggle the `hidden` class on the `taskForm` element.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `classList` to toggle the class `hidden` on `taskForm`.
|
||||
|
||||
```js
|
||||
const splitter = code.split("taskData.forEach")
|
||||
assert.match(splitter[1], /taskForm\.classList\.toggle\(('|")hidden\1\)/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(({id, title, date, description}) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
});
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
---
|
||||
id: 64ec9c55fdeef78bacd2fc3b
|
||||
title: Step 24
|
||||
challengeType: 0
|
||||
dashedName: step-24
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
If you attempt to add another task now, you'll notice that the input fields retain the values you entered for the previous task. To resolve this, you need to clear the input fields after adding a task.
|
||||
|
||||
Instead of clearing the input fields one by one, it's a good practice to create a function that handles clearing those fields. You can then call this function whenever you need to clear the input fields again.
|
||||
|
||||
Use arrow syntax to create a `reset` function and set it to a pair of curly braces.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` and arrow syntax to create a `reset` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+reset\s*=\s*\(\)\s*=>\s*\{\s*/)
|
||||
```
|
||||
|
||||
Your `reset` function should be empty.
|
||||
|
||||
```js
|
||||
assert.match(reset.toString(), /\(\)\s*\{\s*\}/);
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(({id, title, date, description}) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
```
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
---
|
||||
id: 64fac365aeb8ad70b69b366f
|
||||
title: Step 25
|
||||
challengeType: 0
|
||||
dashedName: step-25
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Inside the `reset` function, set each value of `titleInput`, `dateInput`, `descriptionInput` to an empty string.
|
||||
|
||||
Also, use `classList` to toggle the class `hidden` on the `taskForm` and set `currentTask` to an empty object. That's because at this point, `currentTask` will be filled with the task the user might have added.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should set `titleInput.value` to an empty string.
|
||||
|
||||
```js
|
||||
assert.match(reset.toString(), /titleInput\.value\s*=\s*('|")\1;?/)
|
||||
```
|
||||
|
||||
You should set `dateInput.value` to an empty string.
|
||||
|
||||
```js
|
||||
assert.match(reset.toString(), /dateInput\.value\s*=\s*('|")\1;?/)
|
||||
```
|
||||
|
||||
You should set `descriptionInput.value` to an empty string.
|
||||
|
||||
```js
|
||||
assert.match(reset.toString(), /descriptionInput\.value\s*=\s*('|")\1;?/)
|
||||
```
|
||||
|
||||
You should use `classList` to toggle the class `hidden` on `taskForm`
|
||||
|
||||
```js
|
||||
assert.match(reset.toString(), /taskForm\.classList\.toggle\(('|")hidden\1\)/)
|
||||
```
|
||||
|
||||
You should set `currentTask` to an empty object.
|
||||
|
||||
```js
|
||||
assert.match(reset.toString(), /currentTask\s*=\s*\{\};?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
--fcc-editable-region--
|
||||
const reset = () => {
|
||||
|
||||
}
|
||||
--fcc-editable-region--
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
```
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
---
|
||||
id: 64fac4d1773e7a719b1254de
|
||||
title: Step 26
|
||||
challengeType: 0
|
||||
dashedName: step-26
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Remove the existing code toggling the class of `hidden` on `taskForm` and call the `reset` function instead. This would clear the input fields and also hide the form modal for the user to see the added task.
|
||||
|
||||
# --hints--
|
||||
|
||||
Yous should remove the code toggling the `hidden` class on `taskForm`.
|
||||
|
||||
```js
|
||||
const splitter = code.split('<button type="button" class="btn">Delete</button>')
|
||||
assert.notMatch(splitter[1], /taskForm\.classList\.toggle\(("|')hidden\1\);?/)
|
||||
```
|
||||
|
||||
You should call the `reset` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /reset\(\)/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
|
||||
--fcc-editable-region--
|
||||
taskForm.classList.toggle("hidden");
|
||||
--fcc-editable-region--
|
||||
});
|
||||
```
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
---
|
||||
id: 64fac6a497811572b338e5e5
|
||||
title: Step 27
|
||||
challengeType: 0
|
||||
dashedName: step-27
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Also, remove the existing code toggling the class `hidden` on `taskForm` inside the `discardBtn` event listener and call the `reset` function instead. That's because when you click the `Discard` button, everything in the input fields should go away.
|
||||
|
||||
# --hints--
|
||||
|
||||
Yous should remove the code toggling the class `hidden` on `taskForm`.
|
||||
|
||||
```js
|
||||
const splitter = code.split("confirmCloseDialog.close();")
|
||||
assert.notMatch(splitter[1], /taskForm\.classList\.toggle\(("|')hidden\1\);?/)
|
||||
```
|
||||
|
||||
You should call the `reset` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /confirmCloseDialog\.close\(\);?\s*reset\(\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
--fcc-editable-region--
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
--fcc-editable-region--
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
|
||||
reset()
|
||||
});
|
||||
```
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
---
|
||||
id: 64faca774fd9fd74bc084cc9
|
||||
title: Step 28
|
||||
challengeType: 0
|
||||
dashedName: step-28
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You should display the `Cancel` and `Discard` buttons to the user only if there is some text present in the input fields.
|
||||
|
||||
To begin, within the `closeTaskFormBtn` event listener, create a `formInputsContainValues` variable to check if there is a value in the `titleInput` field **or** the `dateInput` field **or** the `descriptionInput` field.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` to create a variable `formInputsContainValues` with the value `titleInput.value || dateInput.value || descriptionInput.value;`
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*formInputsContainValues\s*=\s*titleInput\.value\s*\|\|\s*dateInput\.value\s*\|\|\s*descriptionInput\.value;?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
--fcc-editable-region--
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
|
||||
});
|
||||
--fcc-editable-region--
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
|
||||
reset()
|
||||
});
|
||||
```
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
---
|
||||
id: 64facf6180824876f70a2e86
|
||||
title: Step 29
|
||||
challengeType: 0
|
||||
dashedName: step-29
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Create an `if` statement to check if `formInputsContainValues` is true. If `formInputsContainValues` is true, indicating that there are changes, use the `showModal()` method on `confirmCloseDialog`. Otherwise, if there are no changes, call the `reset()` function to clear the input fields and hide the form modal.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create an `if` statement with the condition `formInputsContainValues`.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(formInputsContainValues\)\s*\{/)
|
||||
```
|
||||
|
||||
The `if` block of your `if` statement should contain `confirmCloseDialog.showModal();`.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(formInputsContainValues\)\s*\{\s*confirmCloseDialog\.showModal\(\);?/)
|
||||
```
|
||||
|
||||
Your `if` statement should have an `else` block.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(formInputsContainValues\)\s*\{\s*confirmCloseDialog\.showModal\(\);?\s*\}\s*else\s*\{/)
|
||||
```
|
||||
|
||||
You should call the `reset()` function in the `else` block of your `if` statement.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(formInputsContainValues\)\s*\{\s*confirmCloseDialog\.showModal\(\);?\s*\}\s*else\s*\{\s*reset\(\);?\s*\}/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
--fcc-editable-region--
|
||||
confirmCloseDialog.showModal();
|
||||
--fcc-editable-region--
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
|
||||
reset()
|
||||
});
|
||||
```
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
---
|
||||
id: 64fad07f43a101779cb8692a
|
||||
title: Step 30
|
||||
challengeType: 0
|
||||
dashedName: step-30
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You can enhance code readability and maintainability by refactoring the `submit` event listener into two separate functions. The first function can be used to add the input values to `taskData`, while the second function can be responsible for adding the tasks to the DOM.
|
||||
|
||||
Use arrow syntax to create an `addOrUpdateTask` function. Then move the `dataArrIndex` variable, the `taskObj` object, and the `if` statement into the `addOrUpdateTask` function.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` and arrow syntax to create an `addOrUpdateTask` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+addOrUpdateTask\s*=\s*\(\)\s*=>\s*\{\s*/)
|
||||
```
|
||||
|
||||
You should move the `dataArrIndex` variable into the `addOrUpdateTask` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+addOrUpdateTask\s*=\s*\(\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\(item\)\s*=>\s*item\.id\s===\s*currentTask\.id\);?/)
|
||||
```
|
||||
|
||||
You should move the `taskObj` object into the `addOrUpdateTask` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+addOrUpdateTask\s*=\s*\(\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\(item\)\s*=>\s*item\.id\s===\s*currentTask\.id\);?\s*const\s*taskObj\s*=\s*\{\s*id:\s*`\$\{titleInput\.value\.toLowerCase\(\)\.split\(('|")\s{1}\1\)\.join\(\1-\1\)\}-\$\{Date\.now\(\)\}`,\s*title:\s*titleInput\.value,\s*date:\s*dateInput\.value,\s*description:\s*descriptionInput\.value,\s*\};?/)
|
||||
```
|
||||
|
||||
You should move the `if` statement with the condition `dataArrIndex === 1` into your `addOrUpdateTask` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+addOrUpdateTask\s*=\s*\(\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\(item\)\s*=>\s*item\.id\s===\s*currentTask\.id\);?\s*const\s*taskObj\s*=\s*\{\s*id:\s*`\$\{titleInput\.value\.toLowerCase\(\)\.split\(('|")\s{1}\1\)\.join\(\1-\1\)\}-\$\{Date\.now\(\)\}`,\s*title:\s*titleInput\.value,\s*date:\s*dateInput\.value,\s*description:\s*descriptionInput\.value,\s*\};?\s*if\s*\(dataArrIndex\s*===\s*-1\)\s*\{\s*taskData\.unshift\(taskObj\);?\s*\}\s*\};?/)
|
||||
```
|
||||
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
|
||||
reset()
|
||||
});
|
||||
```
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
---
|
||||
id: 64fad9cd2eeb1e7ca2ca8c8b
|
||||
title: Step 31
|
||||
challengeType: 0
|
||||
dashedName: step-31
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Use arrow syntax to create an `updateTaskContainer` function. Then move the `taskData.forEach()` and its content into it.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` and arrow syntax to create a `updateTaskContainer` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*updateTaskContainer\s*=\s*\(\)\s*=>\s*\{/)
|
||||
```
|
||||
|
||||
You should move `taskData.forEach()` and its content into the `updateTaskContainer()` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+updateTaskContainer\s+=\s*\(\)\s*=>\s*\{\s*taskData\.forEach\(\s*\(\{\s*id,\s*title,\s*date,\s*description\s*\}\)\s*=>\s*\(tasksContainer\.innerHTML\s*\+=\s*`\s*<div\s+class=('|")task\1\s*id=\1\$\{id\}\1>\s*<p><strong>Title:<\/strong>\s*\$\{title\}<\/p>\s*<p><strong>Date:<\/strong>\s*\$\{date\}<\/p>\s*<p><strong>Description:<\/strong>\s*\$\{description\}<\/p>\s*<button\s+type=\1button\1\s*class=\1btn\1>Edit<\/button>\s*<button\s+type=\1button\1\s*class=\1btn\1>Delete<\/button>\s*<\/div>\s*`\)\s*\);?\s*\};?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
};
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
|
||||
reset()
|
||||
});
|
||||
```
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
---
|
||||
id: 64fadae4f2d51b7d5d8b98d8
|
||||
title: Step 32
|
||||
challengeType: 0
|
||||
dashedName: step-32
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Inside the `addOrUpdateTask` function, call the `updateTaskContainer` and `reset` functions.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should call the `updateTaskContainer` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /updateTaskContainer\(\)\s*/)
|
||||
```
|
||||
|
||||
You should call the `reset` function after calling the `updateTaskContainer` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /updateTaskContainer\(\);?\s*reset\(\);?\s*/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
reset()
|
||||
});
|
||||
```
|
||||
+355
@@ -0,0 +1,355 @@
|
||||
---
|
||||
id: 64fadff23375f27ff06c6d40
|
||||
title: Step 33
|
||||
challengeType: 0
|
||||
dashedName: step-33
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now remove the `reset()` call inside the `taskForm` submit event listener and call the `addOrUpdateTask` function instead.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should remove `reset()` and call `addOrUpdateTask()`.
|
||||
|
||||
```js
|
||||
assert.match(code, /addOrUpdateTask\(\)/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
--fcc-editable-region--
|
||||
reset()
|
||||
--fcc-editable-region--
|
||||
});
|
||||
```
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
---
|
||||
id: 64fae068bcdc9c805bd8399e
|
||||
title: Step 35
|
||||
challengeType: 0
|
||||
dashedName: step-35
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
To enable editing and deleting for each task, add an `onclick` attribute to both buttons. Set the value of the `onclick` attribute to `editTask(this)` for the `Edit` button and `deleteTask(this)` for the `Delete` button. The `editTask(this)` function will handle editing, while the `deleteTask(this)` function will handle deletion.
|
||||
|
||||
`this` is a keyword that refers to the current context. In this case, `this` points to the element that triggers the event – the buttons.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should add `onclick="editTask(this)"` as the first attribute of the edit button.
|
||||
|
||||
```js
|
||||
assert.match(code, /<button\s*onclick=('|")editTask\(this\)\1\s*type=\1button\1\s*class=\1btn\1>Edit<\/button>/)
|
||||
```
|
||||
|
||||
You should add `onclick="deleteTask(this)"` as the first attribute of the delete button.
|
||||
|
||||
```js
|
||||
assert.match(code, /<button\s*onclick=('|")deleteTask\(this\)\1\s*type=\1button\1\s*class=\1btn\1>Delete<\/button>/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
--fcc-editable-region--
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
--fcc-editable-region--
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+371
@@ -0,0 +1,371 @@
|
||||
---
|
||||
id: 64faedcd16a1e985c4c2dc94
|
||||
title: Step 36
|
||||
challengeType: 0
|
||||
dashedName: step-36
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Create a `deleteTask` function using arrow syntax. Pass `buttonEl` as the parameter and define an empty set of curly braces for the function body.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` and arrow syntax to create a `deleteTask` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*deleteTask\s*=\s*\(.*\)\s*=>\s*\{\s*/)
|
||||
```
|
||||
|
||||
Your `deleteTask` function should take a `buttonEl` parameter.
|
||||
|
||||
```js
|
||||
assert.match(deleteTask.toString(), /\(\s*buttonEl\s*\)/);
|
||||
```
|
||||
|
||||
Your `deleteTask` function should be empty.
|
||||
|
||||
```js
|
||||
assert.match(deleteTask.toString(), /\(\s*buttonEl\s*\)\s*\{\s*\}/);
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+359
@@ -0,0 +1,359 @@
|
||||
---
|
||||
id: 64faf0418e828c0114a558a7
|
||||
title: Step 34
|
||||
challengeType: 0
|
||||
dashedName: step-34
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
There's a problem. If you add a task, and then add another, the previous task gets duplicated. This means you need to clear out the existing contents of `tasksContainer` before adding a new task.
|
||||
|
||||
Set the `innerHTML` of `taskContainer` back to an empty string.
|
||||
|
||||
|
||||
# --hints--
|
||||
|
||||
You should set the `innerHTML` of `tasksContainer` to an empty string.
|
||||
|
||||
```js
|
||||
assert.match(code, /tasksContainer\.innerHTML\s*=\s*("|')\1;?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button type="button" class="btn">Edit</button>
|
||||
<button type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
---
|
||||
id: 64faf65b22ad8d07df9be14d
|
||||
title: Step 37
|
||||
challengeType: 0
|
||||
dashedName: step-37
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You need to find the index of the task you want to delete first.
|
||||
|
||||
Create a `dataArrIndex` variable and set its value using the `findIndex()` method on the `taskData` array. Pass `item` as the parameter for the arrow callback function, and within the callback, check if the `id` of `item` is equal to the `id` of the `parentElement` of `buttonEl`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should not alter the `deleteTask` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*deleteTask\s*=\s*\(buttonEl\)\s*=>\s*\{\s*/)
|
||||
```
|
||||
|
||||
You should use `const` to declare a `dataArrIndex` variable and set it to `taskData.findIndex()`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*deleteTask\s*=\s*\(buttonEl\)\s*=>\s*\{\s*const dataArrIndex\s*=\s*taskData\.findIndex\(/)
|
||||
```
|
||||
|
||||
You should pass in `item` as the parameter of the `findIndex()` arrow function callback.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*deleteTask\s*=\s*\(buttonEl\)\s*=>\s*\{\s*const dataArrIndex\s*=\s*taskData\.findIndex\(\s*\(?item\)?/)
|
||||
```
|
||||
|
||||
Your arrow function callback should check if `item.id === buttonEl.parentElement.id`. Don't use curly braces.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*deleteTask\s*=\s*\(buttonEl\)\s*=>\s*\{\s*const dataArrIndex\s*=\s*taskData\.findIndex\(\s*\(?\s*item\s*\)?\s*=>\s*item\.id\s*===\s*buttonEl\.parentElement\.id\s*\);?\s*\};?/)
|
||||
```
|
||||
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
--fcc-editable-region--
|
||||
const deleteTask = (buttonEl) => {
|
||||
|
||||
}
|
||||
--fcc-editable-region--
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
---
|
||||
id: 64faf874364ec308f875f636
|
||||
title: Step 38
|
||||
challengeType: 0
|
||||
dashedName: step-38
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You need to remove the task from the DOM using `remove()` and from the `taskData` array using `splice()`.
|
||||
|
||||
`splice()` is an array method that modifies arrays by removing, replacing, or adding elements at a specified index, while also returning the removed elements. It can take up to three parameters: the first one is the mandatory index at which to start, the second is the number of items to remove, and the third is an optional replacement element. Here's an example:
|
||||
|
||||
```js
|
||||
const fruits = ["mango", "date", "cherry", "banana", "apple"];
|
||||
|
||||
// Remove date and cherry from the array starting at index 1
|
||||
const removedFruits = fruits.splice(1, 2);
|
||||
|
||||
console.log(fruits); // [ 'mango', 'banana', 'apple' ]
|
||||
console.log(removedFruits); // [ 'date', 'cherry' ]
|
||||
```
|
||||
|
||||
Use the `remove()` method to remove the `parentElement` of the `buttonEl` from the DOM. Then use `splice()` to remove the task from the `taskData` array. Pass in `dataArrIndex` and `1` as the parameters of your `splice()`.
|
||||
|
||||
`dataArrIndex` is the index to start and `1` is the number of items to remove.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use the `remove()` method to remove the parent element of `buttonEl`.
|
||||
|
||||
```js
|
||||
assert.match(deleteTask.toString(), /buttonEl\.parentElement\.remove\(\);?/)
|
||||
```
|
||||
|
||||
You should use `splice()` on the `taskData` array.
|
||||
|
||||
```js
|
||||
assert.match(deleteTask.toString(), /taskData\.splice\(/)
|
||||
```
|
||||
|
||||
The first parameter of your `splice()` method should be `dataArrIndex`.
|
||||
|
||||
```js
|
||||
assert.match(deleteTask.toString(), /taskData\.splice\(dataArrIndex/)
|
||||
```
|
||||
|
||||
The second parameter of your `splice()` method should be `1`.
|
||||
|
||||
```js
|
||||
assert.match(deleteTask.toString(), /taskData\.splice\(dataArrIndex,\s*1\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
--fcc-editable-region--
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
}
|
||||
--fcc-editable-region--
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
---
|
||||
id: 64fafac95328110a69bcb75f
|
||||
title: Step 39
|
||||
challengeType: 0
|
||||
dashedName: step-39
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Use arrow syntax to create an `editTask` function. Pass in `buttonEl` as the parameter and add empty curly braces for the body.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` and arrow syntax to create an `editTask` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*editTask\s*=\s*\(.*\)\s*=>\s*\{\s*/)
|
||||
```
|
||||
|
||||
Your `editTask` function should take a `buttonEl` parameter.
|
||||
|
||||
```js
|
||||
assert.match(editTask.toString(), /\(\s*buttonEl\s*\)/);
|
||||
```
|
||||
|
||||
Your `editTask` function should be empty.
|
||||
|
||||
```js
|
||||
assert.match(editTask.toString(), /\(\s*buttonEl\s*\)\s*\{\s*\}/);
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
---
|
||||
id: 64fb0fa0968f2b113b2d90e9
|
||||
title: Step 40
|
||||
challengeType: 0
|
||||
dashedName: step-40
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
As you did in the `deleteTask` function, you need to find the index of the task to be edited.
|
||||
|
||||
Create a `dataArrIndex` variable. For its value, utilize the `findIndex()` method on `taskData`. Pass `item` as the parameter to its callback function and check if the `id` of `item` is equal to the `id` of the `parentElement` of `buttonEl`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should not alter the `editTask` function.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*editTask\s*=\s*\(buttonEl\)\s*=>\s*\{\s*/)
|
||||
```
|
||||
|
||||
You should use `const` to declare a `dataArrIndex` variable and set it to `taskData.findIndex()`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*editTask\s*=\s*\(buttonEl\)\s*=>\s*\{\s*const dataArrIndex\s*=\s*taskData\.findIndex\(/)
|
||||
```
|
||||
|
||||
You should pass in `item` as the parameter of the `findIndex()` arrow function callback. Don't use curly braces.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*editTask\s+=\s*\(buttonEl\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\(item\)/)
|
||||
```
|
||||
|
||||
Your arrow function callback should check if `item.id === buttonEl.parentElement.id`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*editTask\s+=\s*\(buttonEl\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\(item\)\s*=>\s*item\.id\s*===\s*buttonEl\.parentElement\.id\);?\s*\};?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
--fcc-editable-region--
|
||||
const editTask = (buttonEl) => {
|
||||
|
||||
}
|
||||
--fcc-editable-region--
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+373
@@ -0,0 +1,373 @@
|
||||
---
|
||||
id: 64fb1061ca838611ed6a7d6b
|
||||
title: Step 41
|
||||
challengeType: 0
|
||||
dashedName: step-41
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Use square bracket notation to retrieve the task to be edited from the `taskData` array using the `dataArrIndex`. Then, assign it to the `currentTask` object to keep track of it.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should assign `taskData[dataArrIndex]` to `currentTask`.
|
||||
|
||||
```js
|
||||
assert.match(code, /currentTask\s*=\s*taskData\[dataArrIndex\];?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
--fcc-editable-region--
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === buttonEl.parentElement.id);
|
||||
|
||||
|
||||
}
|
||||
--fcc-editable-region--
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
---
|
||||
id: 64fb1321e189a6136d200f77
|
||||
title: Step 42
|
||||
challengeType: 0
|
||||
dashedName: step-42
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The task to be edited is now in the `currentTask` object. Stage it for editing inside the input fields by setting the value of `titleInput` to `currentTask.title`, `dateInput` to `currentTask.date`, and `descriptionInput` to `currentTask.description`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should set `titleInput.value` to `currentTask.title`.
|
||||
|
||||
```js
|
||||
assert.match(editTask.toString(), /titleInput\.value\s*=\s*currentTask\.title;?/)
|
||||
```
|
||||
|
||||
You should set `dateInput.value` to `currentTask.date`.
|
||||
|
||||
```js
|
||||
assert.match(editTask.toString(), /dateInput\.value\s*=\s*currentTask\.date;?/)
|
||||
```
|
||||
|
||||
You should set `descriptionInput.value` to `currentTask.description`.
|
||||
|
||||
```js
|
||||
assert.match(editTask.toString(), /descriptionInput\.value\s*=\s*currentTask\.description;?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
--fcc-editable-region--
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
|
||||
}
|
||||
--fcc-editable-region--
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
---
|
||||
id: 64fb1436adef3e145b4c3501
|
||||
title: Step 43
|
||||
challengeType: 0
|
||||
dashedName: step-43
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Set the `innerText` of the `addOrUpdateTaskBtn` button to `Update Task` and its `aria-label` to `Update Task` as well.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should set the inner text of the `addOrUpdateTaskBtn` button to `Update Task`
|
||||
|
||||
```js
|
||||
assert.match(editTask.toString(), /addOrUpdateTaskBtn\.innerText\s*=\s*("|')Update Task\1;?/)
|
||||
```
|
||||
|
||||
You should set the `ariaLabel` of the `addOrUpdateTaskBtn` button to `Update Task`
|
||||
|
||||
```js
|
||||
assert.match(editTask.toString(), /addOrUpdateTaskBtn\.ariaLabel\s*=\s*("|')Update Task\1;?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
--fcc-editable-region--
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
}
|
||||
--fcc-editable-region--
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
---
|
||||
id: 64fb14d890415c14f93069ce
|
||||
title: Step 44
|
||||
challengeType: 0
|
||||
dashedName: step-44
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Finally, display the `form` modal with the values of the input fields by using `classList` to toggle the `hidden` class on `taskForm`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `classList` to toggle the class `hidden` on `taskForm`.
|
||||
|
||||
```js
|
||||
assert.match(editTask.toString(), /taskForm\.classList\.toggle\(('|")hidden\1\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
--fcc-editable-region--
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
|
||||
}
|
||||
--fcc-editable-region--
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
---
|
||||
id: 64fb154a7c48cd159924bb18
|
||||
title: Step 45
|
||||
challengeType: 0
|
||||
dashedName: step-45
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
At this point, editing a task won't reflect when you submit the task. To make the editing functional, go back to the `if` statement inside the `addOrUpdateTask` function. Create an `else` block and set `taskData[dataArrIndex]` to `taskObj`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `if` statement should have an `else` block.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(dataArrIndex\s*===\s*-1\)\s*\{\s*taskData\.unshift\(taskObj\);?\s*\}\s*else\s*\{\s*/)
|
||||
```
|
||||
|
||||
Your `else` block should have the code `taskData[dataArrIndex] = taskObj`.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(dataArrIndex\s*===\s*-1\)\s*\{\s*taskData\.unshift\(taskObj\);?\s*\}\s*else\s*\{\s*taskData\[dataArrIndex\]\s*=\s*taskObj;?\s*\}/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
--fcc-editable-region--
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+401
@@ -0,0 +1,401 @@
|
||||
---
|
||||
id: 64fb1c4dc0feb219149a7c7d
|
||||
title: Step 46
|
||||
challengeType: 0
|
||||
dashedName: step-46
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
If the user attempts to edit a task but decides not to make any changes before closing the form, there is no need to display the modal with the `Cancel` and `Discard` buttons.
|
||||
|
||||
Inside the `closeTaskFormBtn` event listener, use `const` to create another variable named `formInputValuesUpdated`. Check if the user made changes while trying to edit a task by verifying that the `titleInput` value **is not equal to** `currentTask.title`, the `dateInput` value **is not equal to** `currentTask.date`, and the `descriptionInput` value **is not equal to** `currentTask.description`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `formInputValuesUpdated` variable should check if `titleInput.value` is not equal to `currentTask.title`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*formInputValuesUpdated\s*=\s*titleInput\.value\s*!==\s*currentTask\.title\s*/)
|
||||
```
|
||||
|
||||
Your `formInputValuesUpdated` variable should check if `titleInput.value` is not equal to `currentTask.title` or `dateInput.value` is not equal to `currentTask.date`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*formInputValuesUpdated\s*=\s*titleInput\.value\s*!==\s*currentTask\.title\s*\|\|\s*dateInput\.value\s*!==\s*currentTask\.date/)
|
||||
```
|
||||
|
||||
Your `formInputValuesUpdated` variable should have the value `titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*formInputValuesUpdated\s*=\s*titleInput\.value\s*!==\s*currentTask\.title\s*\|\|\s*dateInput\.value\s*!==\s*currentTask\.date\s*\|\|\s*descriptionInput\.value\s*!==\s*currentTask\.description;?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
---
|
||||
id: 64fb285637fa1e1c222033e3
|
||||
title: Step 47
|
||||
challengeType: 0
|
||||
dashedName: step-47
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now add `formInputValuesUpdated` as the second mandatory condition in the `if` statement using the `AND` operator.
|
||||
|
||||
This way, the `Cancel` and `Discard` buttons in the modal won't be displayed to the user if they haven't made any changes to the input fields while attempting to edit a task.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `if` should have the condition `formInputsContainValues && formInputValuesUpdated`.
|
||||
|
||||
```js
|
||||
assert.match(code.split("if (formInputsContainValues"), /\s*&&\s*formInputValuesUpdated/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
--fcc-editable-region--
|
||||
if (formInputsContainValues) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
--fcc-editable-region--
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+414
@@ -0,0 +1,414 @@
|
||||
---
|
||||
id: 64fb29348a60361ccd45c1e2
|
||||
title: Step 48
|
||||
challengeType: 0
|
||||
dashedName: step-48
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
`localStorage` offers methods for saving, retrieving, and deleting items. The items you save can be of any JavaScript data type.
|
||||
|
||||
For instance, the `setItem()` method is used to save an item, and the `getItem()` method retrieves the item. To delete a specific item, you can utilize the `removeItem()` method, or if you want to delete all items in the storage, you can use `clear()`.
|
||||
|
||||
Here's how you can save an item:
|
||||
|
||||
```js
|
||||
localStorage.setItem("key", value); // value could be string, number, or any other data type
|
||||
```
|
||||
|
||||
A `myTaskArr` array has been provided for you. Use the `setItem()` method to save it with a key of `data`.
|
||||
|
||||
After that, open your browser console and go to the `Applications` tab, select `Local Storage`, and the freeCodeCamp domain you see.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `localStorage.setItem()` should have a key of `"data"`.
|
||||
|
||||
```js
|
||||
assert.match(code, /localStorage\.setItem\(("|')data\1/)
|
||||
```
|
||||
|
||||
Your `localStorage.setItem()` should have a key of `myTaskArr`.
|
||||
|
||||
```js
|
||||
assert.match(code, /localStorage\.setItem\(("|')data\1,\s*myTaskArr\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
|
||||
const myTaskArr = [
|
||||
{ task: "Walk the Dog", date: "22-04-2022" },
|
||||
{ task: "Read some books", date: "02-11-2023" },
|
||||
{ task: "Watch football", date: "10-08-2021" },
|
||||
];
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+404
@@ -0,0 +1,404 @@
|
||||
---
|
||||
id: 64fefebad99209211ec30537
|
||||
title: Step 49
|
||||
challengeType: 0
|
||||
dashedName: step-49
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
If you check the `Application` tab of your browser console, you'll notice a series of `[object Object]`. This is because everything you save in `localStorage` needs to be in string format.
|
||||
|
||||
To resolve the issue, wrap the data you're saving in the `JSON.stringify()` method. Then, check local storage again to observe the results.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `localStorage.setItem()` should have a key of `"data"`.
|
||||
|
||||
```js
|
||||
assert.match(code, /localStorage\.setItem\(("|')data\1/)
|
||||
```
|
||||
|
||||
You should wrap `JSON.stringify()` around `myTaskArr`.
|
||||
|
||||
```js
|
||||
assert.match(code, /localStorage\.setItem\(("|')data\1,\s*JSON\.stringify\(myTaskArr\)\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
|
||||
const myTaskArr = [
|
||||
{ task: "Walk the Dog", date: "22-04-2022" },
|
||||
{ task: "Read some books", date: "02-11-2023" },
|
||||
{ task: "Watch football", date: "10-08-2021" },
|
||||
];
|
||||
|
||||
--fcc-editable-region--
|
||||
localStorage.setItem("data", myTaskArr);
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+406
@@ -0,0 +1,406 @@
|
||||
---
|
||||
id: 64ff0313700dad264d19dfe4
|
||||
title: Step 50
|
||||
challengeType: 0
|
||||
dashedName: step-50
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now that you have the `myTaskArr` array saved in `localStorage` correctly, you can retrieve it with `getItem()` by specifying the key you used to save the item.
|
||||
|
||||
Use the `getItem()` method to retrieve the `myTaskArr` array and assign it to the variable `getTaskArr`. Then, log the `getTaskArr` variable to the console to see the result.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` to create a `getTaskArr` variable and assign `localStorage.getItem("data")` to it.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*getTaskArr\s*=\s*localStorage\.getItem\(('|")data\1\);?/)
|
||||
```
|
||||
|
||||
You should log the `getTaskArr` variable to the console.
|
||||
|
||||
```js
|
||||
assert.match(code, /console\.log\(getTaskArr\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
|
||||
const myTaskArr = [
|
||||
{ task: "Walk the Dog", date: "22-04-2022" },
|
||||
{ task: "Read some books", date: "02-11-2023" },
|
||||
{ task: "Watch football", date: "10-08-2021" },
|
||||
];
|
||||
|
||||
localStorage.setItem("data", JSON.stringify(myTaskArr));
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+411
@@ -0,0 +1,411 @@
|
||||
---
|
||||
id: 64ff04cc33779427a6412449
|
||||
title: Step 51
|
||||
challengeType: 0
|
||||
dashedName: step-51
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The item you retrieve is a string, as you saved it with `JSON.stringify()`. To view it in its original form before saving, you need to use `JSON.parse()`.
|
||||
|
||||
Use `getItem()` to retrieve the `myTaskArr` array again. This time, wrap it inside `JSON.parse()`, assign it to the variable `getTaskArrObj` and log the `getTaskArrObj` to the console.
|
||||
|
||||
Check the console to see the difference between `getTaskArr` and `getTaskObj`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` to create a `getTaskArrObj` variable and assign it to `JSON.parse(localStorage.getItem('data'));`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*getTaskArrObj\s*=\s*JSON\.parse\(localStorage\.getItem\(('|")data\1\)\);?/)
|
||||
```
|
||||
|
||||
You should log the `getTaskArrObj` variable to the console.
|
||||
|
||||
```js
|
||||
assert.match(code, /console\.log\(getTaskArrObj\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
|
||||
const myTaskArr = [
|
||||
{ task: "Walk the Dog", date: "22-04-2022" },
|
||||
{ task: "Read some books", date: "02-11-2023" },
|
||||
{ task: "Watch football", date: "10-08-2021" },
|
||||
];
|
||||
|
||||
localStorage.setItem("data", JSON.stringify(myTaskArr));
|
||||
|
||||
const getTaskArr = localStorage.getItem("data")
|
||||
console.log(getTaskArr)
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+406
@@ -0,0 +1,406 @@
|
||||
---
|
||||
id: 64ff068e0426eb288874ed79
|
||||
title: Step 52
|
||||
challengeType: 0
|
||||
dashedName: step-52
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You can use `localStorage.removeItem()` to remove a specific item and `localStorage.clear()` to clear all items in the local storage.
|
||||
|
||||
Remove the `data` item from local storage and open the console to observe the result. You should see `null`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `localStorage.removeItem()` to remove the `data` item from the browser's local storage.
|
||||
|
||||
```js
|
||||
assert.match(code, /localStorage\.removeItem\(('|")data\1\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
|
||||
const myTaskArr = [
|
||||
{ task: "Walk the Dog", date: "22-04-2022" },
|
||||
{ task: "Read some books", date: "02-11-2023" },
|
||||
{ task: "Watch football", date: "10-08-2021" },
|
||||
];
|
||||
|
||||
localStorage.setItem("data", JSON.stringify(myTaskArr));
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
const getTaskArr = localStorage.getItem("data")
|
||||
console.log(getTaskArr)
|
||||
|
||||
const getTaskArrObj = JSON.parse(localStorage.getItem("data"));
|
||||
console.log(getTaskArrObj);
|
||||
```
|
||||
+413
@@ -0,0 +1,413 @@
|
||||
---
|
||||
id: 64ff23daf176a92de95f24dc
|
||||
title: Step 53
|
||||
challengeType: 0
|
||||
dashedName: step-53
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Using `localStorage.clear()` won't just delete a single item from local storage but will remove all items.
|
||||
|
||||
Remove `localStorage.removeItem()` and use `localStorage.clear()` instead. You don't need to pass in anything. You should also see `null` in the console.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should remove `localStorage.removeItem("data")`.
|
||||
|
||||
```js
|
||||
assert.notMatch(code, /localStorage\.removeItem\(('|")data\1\);/)
|
||||
```
|
||||
|
||||
You should remove everything from the browser `local storage` with `localStorage.clear()`.
|
||||
|
||||
```js
|
||||
assert.match(code, /localStorage\.clear\(\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
|
||||
const myTaskArr = [
|
||||
{ task: "Walk the Dog", date: "22-04-2022" },
|
||||
{ task: "Read some books", date: "02-11-2023" },
|
||||
{ task: "Watch football", date: "10-08-2021" },
|
||||
];
|
||||
|
||||
localStorage.setItem("data", JSON.stringify(myTaskArr));
|
||||
|
||||
--fcc-editable-region--
|
||||
localStorage.removeItem("data");
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
const getTaskArr = localStorage.getItem("data")
|
||||
console.log(getTaskArr)
|
||||
|
||||
const getTaskArrObj = JSON.parse(localStorage.getItem("data"));
|
||||
console.log(getTaskArrObj);
|
||||
```
|
||||
+404
@@ -0,0 +1,404 @@
|
||||
---
|
||||
id: 64ff24b80431f62ec6b93f65
|
||||
title: Step 54
|
||||
challengeType: 0
|
||||
dashedName: step-54
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Remove the `myTaskArr` array and all of the code for `localStorage` because you don't need them anymore.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should remove `myTaskArr` and all the code related to `localStorage` that you've just learned.
|
||||
|
||||
```js
|
||||
assert.notMatch(code, /const\s*myTaskArr\s*=\s*\[\s*\{\s*task:\s"Walk\s*the\s*Dog",\s*date:\s*"22-04-2022"\s*\},\s*\{\s*task:\s"Read\s*some\s*books",\s*date:\s*"02-11-2023"\s*\},\s*\{\s*task:\s"Watch\s*football",\s*date:\s*"10-08-2021"\s*\},\s*\];?\s*localStorage\.setItem\("data", JSON\.stringify\(myTaskArr\)\);\s*localStorage\.clear\(\);?\s*const\s*getTaskArr\s*=\s*localStorage\.getItem\("data"\)\s*console\.log\(getTaskArr\)\s*const\s*getTaskArrObj\s*=\s*JSON\.parse\(localStorage\.getItem\("data"\)\);?\s*console\.log\(getTaskArrObj\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
|
||||
--fcc-editable-region--
|
||||
const myTaskArr = [
|
||||
{ task: "Walk the Dog", date: "22-04-2022" },
|
||||
{ task: "Read some books", date: "02-11-2023" },
|
||||
{ task: "Watch football", date: "10-08-2021" },
|
||||
];
|
||||
|
||||
localStorage.setItem("data", JSON.stringify(myTaskArr));
|
||||
|
||||
localStorage.clear();
|
||||
|
||||
const getTaskArr = localStorage.getItem("data")
|
||||
console.log(getTaskArr)
|
||||
|
||||
const getTaskArrObj = JSON.parse(localStorage.getItem("data"));
|
||||
console.log(getTaskArrObj);
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+404
@@ -0,0 +1,404 @@
|
||||
---
|
||||
id: 65003986d17d1e1865b269c0
|
||||
title: Step 55
|
||||
challengeType: 0
|
||||
dashedName: step-55
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now you should save the task items to local storage when the user adds, updates, or removes a task.
|
||||
|
||||
Inside the `addOrUpdateTask` function, use `setItem()` to save the tasks with a key of `data`, then pass the `taskData` array as its parameter. Ensure that you stringify the `taskData`.
|
||||
|
||||
This would persist data once the user adds or updates tasks.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `localStorage.setItem()` to save the tasks to the browser `local storage`.
|
||||
|
||||
```js
|
||||
assert.match(code, /localStorage\.setItem\(/)
|
||||
```
|
||||
|
||||
You should pass in `"data"` as the first parameter of your `localStorage.setItem()`.
|
||||
|
||||
```js
|
||||
assert.match(code, /localStorage\.setItem\(('|")data\1/)
|
||||
```
|
||||
|
||||
You should pass in `JSON.stringify(taskData)` as the second parameter of your `localStorage.setItem()`.
|
||||
|
||||
```js
|
||||
assert.match(code, /localStorage\.setItem\(('|")data\1,\s*JSON\.stringify\(taskData\)\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+407
@@ -0,0 +1,407 @@
|
||||
---
|
||||
id: 650046832f92c01a35834bca
|
||||
title: Step 56
|
||||
challengeType: 0
|
||||
dashedName: step-56
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You also want a deleted task to be removed from local storage. For this, you don't need the `removeItem()` or `clear()` methods. Since you already use `splice()` to remove the deleted task from `taskData`, all you need to do now is save `taskData` to local storage again.
|
||||
|
||||
Use `setItem()` to save the `taskData` array again. Pass in `data` as the key and ensure that `taskData` is stringified before saving.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `localStorage.setItem()` to save the tasks to the browser `local storage`.
|
||||
|
||||
```js
|
||||
const splitter = code.split("taskData.splice(dataArrIndex, 1);")
|
||||
assert.match(splitter[1], /localStorage\.setItem\(/)
|
||||
```
|
||||
|
||||
You should pass in `"data"` as the first parameter of your `localStorage.setItem()`.
|
||||
|
||||
```js
|
||||
const splitter = code.split("taskData.splice(dataArrIndex, 1);")
|
||||
assert.match(splitter[1], /localStorage\.setItem\(('|")data\1/)
|
||||
```
|
||||
|
||||
You should pass in `JSON.stringify(taskData)` as the second parameter of your `localStorage.setItem()`.
|
||||
|
||||
```js
|
||||
const splitter = code.split("taskData.splice(dataArrIndex, 1);")
|
||||
assert.match(splitter[1], /localStorage\.setItem\(('|")data\1,\s*JSON\.stringify\(taskData\)\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
localStorage.setItem("data", JSON.stringify(taskData));
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+392
@@ -0,0 +1,392 @@
|
||||
---
|
||||
id: 650048b0764f9c1b798200e2
|
||||
title: Step 57
|
||||
challengeType: 0
|
||||
dashedName: step-57
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
If you add, update, or remove a task, it should reflect in the UI. However, that's not happening now because you have yet to retrieve the tasks. To do this, you need to modify your initial `taskData` to be an empty array.
|
||||
|
||||
Set `taskData` to the retrieval of `data` from local storage **or** an empty array. Make sure you parse the data coming with `JSON.parse()` because you saved it as a string.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should set the `taskData` variable to `JSON.parse(localStorage.getItem("data")) || [];`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s*taskData\s*=\s*JSON.parse\(localStorage\.getItem\(('|")data\1\)\)\s*\|\|\s*\[\];?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
--fcc-editable-region--
|
||||
const taskData = [];
|
||||
--fcc-editable-region--
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
localStorage.setItem("data", JSON.stringify(taskData));
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
localStorage.setItem("data", JSON.stringify(taskData));
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+776
@@ -0,0 +1,776 @@
|
||||
---
|
||||
id: 65004ba581d03d1d5628b41c
|
||||
title: Step 58
|
||||
challengeType: 0
|
||||
dashedName: step-58
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You've retrieved the task item(s) now, but they still don't reflect in the UI when the page loads. However, they appear when you add a new task.
|
||||
|
||||
To fix this issue, you can check if there's a task inside `taskData`, and then call the `updateTaskContainer()`.
|
||||
|
||||
Check if there's a task inside `taskData`, then call the `updateTaskContainer()` inside the `if` statement block.
|
||||
|
||||
With that, you've completed this project.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create an `if` statement with the condition `taskData.length`.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(taskData\.length\)\s*\{\s*/)
|
||||
```
|
||||
|
||||
You should call the `updateTaskContainer` function in your `if` statement.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(taskData\.length\)\s*\{\s*updateTaskContainer\(\);?\s*\}/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = JSON.parse(localStorage.getItem("data")) || [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
localStorage.setItem("data", JSON.stringify(taskData));
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
localStorage.setItem("data", JSON.stringify(taskData));
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = JSON.parse(localStorage.getItem("data")) || [];
|
||||
let currentTask = {};
|
||||
|
||||
const addOrUpdateTask = () => {
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
} else {
|
||||
taskData[dataArrIndex] = taskObj;
|
||||
}
|
||||
|
||||
localStorage.setItem("data", JSON.stringify(taskData));
|
||||
updateTaskContainer()
|
||||
reset()
|
||||
};
|
||||
|
||||
const updateTaskContainer = () => {
|
||||
tasksContainer.innerHTML = "";
|
||||
|
||||
taskData.forEach(
|
||||
({ id, title, date, description }) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
<p><strong>Description:</strong> ${description}</p>
|
||||
<button onclick="editTask(this)" type="button" class="btn">Edit</button>
|
||||
<button onclick="deleteTask(this)" type="button" class="btn">Delete</button>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const deleteTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
buttonEl.parentElement.remove();
|
||||
taskData.splice(dataArrIndex, 1);
|
||||
localStorage.setItem("data", JSON.stringify(taskData));
|
||||
}
|
||||
|
||||
const editTask = (buttonEl) => {
|
||||
const dataArrIndex = taskData.findIndex(
|
||||
(item) => item.id === buttonEl.parentElement.id
|
||||
);
|
||||
|
||||
currentTask = taskData[dataArrIndex];
|
||||
|
||||
titleInput.value = currentTask.title;
|
||||
dateInput.value = currentTask.date;
|
||||
descriptionInput.value = currentTask.description;
|
||||
|
||||
addOrUpdateTaskBtn.innerText = "Update Task";
|
||||
addOrUpdateTaskBtn.ariaLabel = "Update Task";
|
||||
|
||||
taskForm.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
titleInput.value = "";
|
||||
dateInput.value = "";
|
||||
descriptionInput.value = "";
|
||||
taskForm.classList.toggle("hidden");
|
||||
currentTask = {};
|
||||
}
|
||||
|
||||
if (taskData.length) {
|
||||
updateTaskContainer();
|
||||
}
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
|
||||
const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
|
||||
|
||||
if (formInputsContainValues && formInputValuesUpdated) {
|
||||
confirmCloseDialog.showModal();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
reset()
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
addOrUpdateTask();
|
||||
});
|
||||
```
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
---
|
||||
id: 650300a25b6f72964ab8aca6
|
||||
title: Step 13
|
||||
challengeType: 0
|
||||
dashedName: step-13
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
To make the `id` more unique, add another hyphen and use `Date.now()`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should attach `-${Date.now()}` to the existing value of the `id` key. Don't forget it needs to be inside the template string.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskObj\s*=\s*\{\s*id:\s*`\$\{titleInput\.value\.toLowerCase\(\)\.split\(('|")\s{1}\1\)\.join\(\1-\1\)\}-\$\{Date\.now\(\)\}`,?\s*\}/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
--fcc-editable-region--
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(' ').join('-')}`,
|
||||
};
|
||||
--fcc-editable-region--
|
||||
});
|
||||
```
|
||||
+337
@@ -0,0 +1,337 @@
|
||||
---
|
||||
id: 65099dbd8f137d58e5c0ff16
|
||||
title: Step 21
|
||||
challengeType: 0
|
||||
dashedName: step-21
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Create one more `p` element and interpolate the `description` you destructured as the text. Also, create a `strong` element inside the paragraph with the text `Description:`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a `p` element with `${description}` as the text.
|
||||
|
||||
```js
|
||||
assert.match(code, /<p>.*\$\{description\}<\/p>/)
|
||||
```
|
||||
|
||||
You should create a `strong` element with the text `Description:` after the opening tag of your `p` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /(?<=<p>)<strong>Description:\s*<\/strong>\s*/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
if (dataArrIndex === -1) {
|
||||
taskData.unshift(taskObj);
|
||||
}
|
||||
|
||||
taskData.forEach(({id, title, date, description}) =>
|
||||
(tasksContainer.innerHTML += `
|
||||
<div class="task" id="${id}">
|
||||
<p><strong>Title:</strong> ${title}</p>
|
||||
<p><strong>Date:</strong> ${date}</p>
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
});
|
||||
```
|
||||
+1
-1
@@ -12,7 +12,7 @@ This video will help you understand the equations of simple and compound interes
|
||||
|
||||
Here is the <a href="https://colab.research.google.com/drive/1IVBaeX84arJXS73raRROaxbz4qMyFVb6?usp=sharing" target="_blank" rel="noopener noreferrer nofollow">Colab notebook to go along with this video.</a>
|
||||
|
||||
Here is an additional <a href="https://colab.research.google.com/drive/1-HWYmzKn6HmEUWMBv7G525CpoQpm8TnN?usp=sharing" target="_blank" rel="noopener noreferrer nofollow">Colab notebook that shows you one way to put many of these interest and payment forumlas into Python functions.</a> Also you will see an example of using some formulas to output results, notice a trend, and follow up with other formulas to analyze patterns.
|
||||
Here is an additional <a href="https://colab.research.google.com/drive/1-HWYmzKn6HmEUWMBv7G525CpoQpm8TnN?usp=sharing" target="_blank" rel="noopener noreferrer nofollow">Colab notebook that shows you one way to put many of these interest and payment formulas into Python functions.</a> Also you will see an example of using some formulas to output results, notice a trend, and follow up with other formulas to analyze patterns.
|
||||
|
||||
# --question--
|
||||
|
||||
|
||||
+2
-2
@@ -71,8 +71,8 @@ class PrimeSeive {
|
||||
// Mark value in seive array
|
||||
const prime = 2 * i + 3;
|
||||
// Mark all multiples of this number as false (not prime)
|
||||
const primeSqaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSqaredIndex; j < upper; j += prime) {
|
||||
const primeSquaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSquaredIndex; j < upper; j += prime) {
|
||||
seive[j] = false;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -73,7 +73,7 @@ substringDivisibility(5);
|
||||
|
||||
```js
|
||||
function substringDivisibility(n) {
|
||||
function isSubDivisable(digits) {
|
||||
function isSubDivisible(digits) {
|
||||
const factors = [2, 3, 5, 7, 11, 13, 17];
|
||||
|
||||
for (let i = 1; i < digits.length - 2; i++) {
|
||||
@@ -108,17 +108,17 @@ function substringDivisibility(n) {
|
||||
}
|
||||
|
||||
const allowedDigits = [...new Array(n + 1).keys()];
|
||||
const divisablePandigitals = [];
|
||||
const divisiblePandigitals = [];
|
||||
heapsPermutations(
|
||||
allowedDigits.length,
|
||||
allowedDigits,
|
||||
isSubDivisable,
|
||||
divisablePandigitals
|
||||
isSubDivisible,
|
||||
divisiblePandigitals
|
||||
);
|
||||
|
||||
let sum = 0;
|
||||
for (let i = 0; i < divisablePandigitals.length; i++) {
|
||||
sum += divisablePandigitals[i];
|
||||
for (let i = 0; i < divisiblePandigitals.length; i++) {
|
||||
sum += divisiblePandigitals[i];
|
||||
}
|
||||
|
||||
return sum;
|
||||
|
||||
+4
-4
@@ -67,8 +67,8 @@ class PrimeSeive {
|
||||
const prime = 2 * i + 3;
|
||||
primes.push(prime);
|
||||
// Mark all multiples of this number as false (not prime)
|
||||
const primeSqaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSqaredIndex; j < upper; j += prime) {
|
||||
const primeSquaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSquaredIndex; j < upper; j += prime) {
|
||||
seive[j] = false;
|
||||
}
|
||||
}
|
||||
@@ -101,10 +101,10 @@ class PrimeSeive {
|
||||
};
|
||||
|
||||
function consecutivePrimeSum(limit) {
|
||||
// Initalize seive
|
||||
// Initialize seive
|
||||
const primeSeive = new PrimeSeive(limit);
|
||||
|
||||
// Initalize for longest sum < 100
|
||||
// Initialize for longest sum < 100
|
||||
let bestPrime = 41;
|
||||
let bestI = 0;
|
||||
let bestJ = 5;
|
||||
|
||||
+2
-2
@@ -67,8 +67,8 @@ class PrimeSeive {
|
||||
// Mark value in seive array
|
||||
const prime = 2 * i + 3;
|
||||
// Mark all multiples of this number as false (not prime)
|
||||
const primeSqaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSqaredIndex; j < upper; j += prime) {
|
||||
const primeSquaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSquaredIndex; j < upper; j += prime) {
|
||||
seive[j] = false;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -31,10 +31,10 @@ For $1 ≤ k ≤ 200$, find $\sum{m(k)}$.
|
||||
|
||||
# --hints--
|
||||
|
||||
`efficientExponentation()` should return `1582`.
|
||||
`efficientExponentiation()` should return `1582`.
|
||||
|
||||
```js
|
||||
assert.strictEqual(efficientExponentation(), 1582);
|
||||
assert.strictEqual(efficientExponentiation(), 1582);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
@@ -42,12 +42,12 @@ assert.strictEqual(efficientExponentation(), 1582);
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
function efficientExponentation() {
|
||||
function efficientExponentiation() {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
efficientExponentation();
|
||||
efficientExponentiation();
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
+2
-2
@@ -67,8 +67,8 @@ class PrimeSeive {
|
||||
// Mark value in seive array
|
||||
const prime = 2 * i + 3;
|
||||
// Mark all multiples of this number as false (not prime)
|
||||
const primeSqaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSqaredIndex; j < upper; j += prime) {
|
||||
const primeSquaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSquaredIndex; j < upper; j += prime) {
|
||||
seive[j] = false;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -35,7 +35,7 @@ concealedSquare();
|
||||
|
||||
```js
|
||||
// Check if n**2 matches the pattern
|
||||
function squareMatchs(n) {
|
||||
function squareMatches(n) {
|
||||
// Need BigInt due to size of values
|
||||
let nSquared = (BigInt(n) * BigInt(n)).toString();
|
||||
|
||||
@@ -55,8 +55,8 @@ function concealedSquare() {
|
||||
for (let x = maxSquareRoot; x >= minSquareRoot; x -= 10) {
|
||||
// Note: 3*3 = 9 and 7*7 = 49 are only trailing digits
|
||||
// that can produce 9 as trailing digit in square
|
||||
if (squareMatchs(x + 3)) return (x + 3)*10;
|
||||
if (squareMatchs(x + 7)) return (x + 7)*10;
|
||||
if (squareMatches(x + 3)) return (x + 3)*10;
|
||||
if (squareMatches(x + 7)) return (x + 7)*10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
+18
-1
@@ -54,7 +54,24 @@ assert.match(code, /myStorage\.car\.inside/);
|
||||
應該用 `const` 聲明 `gloveBoxContents`。
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+gloveBoxContents\s*=\s*myStorage\.car\.inside\[\s*("|')glove box\1\s*\]|const\s*{\s*('|")glove box\2:\s*gloveBoxContents\s*}\s*=\s*myStorage\.car\.inside;/);
|
||||
assert.match(code, /const\s+gloveBoxContents\s*=/);
|
||||
```
|
||||
|
||||
You should not change the `myStorage` object.
|
||||
|
||||
```js
|
||||
const expectedMyStorage = {
|
||||
"car":{
|
||||
"inside":{
|
||||
"glove box":"maps",
|
||||
"passenger seat":"crumbs"
|
||||
},
|
||||
"outside":{
|
||||
"trunk":"jack"
|
||||
}
|
||||
}
|
||||
};
|
||||
assert.deepStrictEqual(myStorage, expectedMyStorage);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ dashedName: catch-misspelled-variable-and-function-names
|
||||
|
||||
`console.log()` 和 `typeof` 方法是檢查中間值和程序輸出類型的兩種主要方法。 現在是時候瞭解一下 bug 出現的常見的情形。 一個語法級別的問題是打字太快帶來的低級拼寫錯誤。
|
||||
|
||||
變量或函數名的錯寫、漏寫或大小寫弄混都會讓瀏覽器嘗試查找並不存在的東西,並報出“引用錯誤”。 JavaScript 變量和函數名稱區分大小寫。
|
||||
Transposed, missing, or miscapitalized characters in a variable or function name will have the browser looking for an object that doesn't exist - and complain in the form of a reference error. JavaScript 變量和函數名稱區分大小寫。
|
||||
|
||||
# --instructions--
|
||||
|
||||
|
||||
+2
-2
@@ -58,8 +58,8 @@ class PrimeSeive {
|
||||
// Mark value in seive array
|
||||
const prime = 2 * i + 3;
|
||||
// Mark all multiples of this number as false (not prime)
|
||||
const primeSqaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSqaredIndex; j < upper; j += prime) {
|
||||
const primeSquaredIndex = 2 * i ** 2 + 6 * i + 3;
|
||||
for (let j = primeSquaredIndex; j < upper; j += prime) {
|
||||
seive[j] = false;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -231,13 +231,13 @@ async (getUserInput) => {
|
||||
};
|
||||
const url = getUserInput('url') + '/api/issues/fcc-project';
|
||||
const itemToUpdate = await $.post(url, initialData);
|
||||
const updateSucccess = await $.ajax({
|
||||
const updateSuccess = await $.ajax({
|
||||
url: url,
|
||||
type: 'PUT',
|
||||
data: { _id: itemToUpdate._id, issue_text: 'New Issue Text' }
|
||||
});
|
||||
assert.isObject(updateSucccess);
|
||||
assert.deepEqual(updateSucccess, {
|
||||
assert.isObject(updateSuccess);
|
||||
assert.deepEqual(updateSuccess, {
|
||||
result: 'successfully updated',
|
||||
_id: itemToUpdate._id
|
||||
});
|
||||
|
||||
+2
-2
@@ -82,7 +82,7 @@ const OPERATORS_ = {
|
||||
"/": (a, b) => a / b,
|
||||
}
|
||||
|
||||
const PRECIDENCE_ = {
|
||||
const PRECEDENCE_ = {
|
||||
"+": 1,
|
||||
"-": 1,
|
||||
"*": 2,
|
||||
@@ -114,7 +114,7 @@ function evaluate_(expression) {
|
||||
case "*":
|
||||
case "/":
|
||||
while (stack.length &&
|
||||
PRECIDENCE_[c] <= PRECIDENCE_[stack[stack.length-1]]) {
|
||||
PRECEDENCE_[c] <= PRECEDENCE_[stack[stack.length-1]]) {
|
||||
postfix += stack.pop();
|
||||
}
|
||||
stack.push(c);
|
||||
|
||||
+3
-3
@@ -175,7 +175,7 @@ function dealFreeCell(seed) {
|
||||
const rng = FreeCellRNG(seed);
|
||||
const deck = getDeck();
|
||||
|
||||
const deltCards = [[], [], [], [], [], [], []];
|
||||
const dealtCards = [[], [], [], [], [], [], []];
|
||||
let currentColumn = 0;
|
||||
let currentRow = 0;
|
||||
|
||||
@@ -195,7 +195,7 @@ function dealFreeCell(seed) {
|
||||
card = deck.pop();
|
||||
|
||||
// Deal this card
|
||||
deltCards[currentRow].push(card);
|
||||
dealtCards[currentRow].push(card);
|
||||
currentColumn += 1;
|
||||
if (currentColumn === 8) {
|
||||
currentColumn = 0;
|
||||
@@ -203,6 +203,6 @@ function dealFreeCell(seed) {
|
||||
}
|
||||
}
|
||||
|
||||
return deltCards;
|
||||
return dealtCards;
|
||||
}
|
||||
```
|
||||
|
||||
+4
-4
@@ -101,7 +101,7 @@ function combinations(possibleNumbers, total) {
|
||||
function combinations(possibleNumbers, total) {
|
||||
let firstNumber;
|
||||
let secondNumber;
|
||||
let thridNumber;
|
||||
let thirdNumber;
|
||||
const allCombinations = [];
|
||||
|
||||
for (let i = 0; i < possibleNumbers.length; i += 1) {
|
||||
@@ -112,10 +112,10 @@ function combinations(possibleNumbers, total) {
|
||||
secondNumber = possibleNumbers[j];
|
||||
|
||||
if (j !== i && firstNumber + secondNumber <= total) {
|
||||
thridNumber = total - firstNumber - secondNumber;
|
||||
thirdNumber = total - firstNumber - secondNumber;
|
||||
|
||||
if (thridNumber !== firstNumber && thridNumber !== secondNumber && possibleNumbers.includes(thridNumber)) {
|
||||
allCombinations.push([firstNumber, secondNumber, thridNumber]);
|
||||
if (thirdNumber !== firstNumber && thirdNumber !== secondNumber && possibleNumbers.includes(thirdNumber)) {
|
||||
allCombinations.push([firstNumber, secondNumber, thirdNumber]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ $PA = LU$
|
||||
|
||||
# --instructions--
|
||||
|
||||
The task is to implement a routine which will take a square nxn matrix $A$ and return a lower triangular matrix $L$, a upper triangular matrix $U$ and a permutation matrix $P$, so that the above equation is fullfilled. The returned value should be in the form `[L, U, P]`.
|
||||
The task is to implement a routine which will take a square nxn matrix $A$ and return a lower triangular matrix $L$, a upper triangular matrix $U$ and a permutation matrix $P$, so that the above equation is fulfilled. The returned value should be in the form `[L, U, P]`.
|
||||
|
||||
# --hints--
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ dashedName: step-82
|
||||
|
||||
企鵝的嘴和腳的 `color` 相同。
|
||||
|
||||
創建一個名爲 `--penguin-picorna` 的新自定義 CSS 變量,並用它替換所有相關的屬性。
|
||||
Create a new custom CSS variable named `--penguin-picorna`, and replace all relevant property values with it.
|
||||
|
||||
# --hints--
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ Inside `pick`, use `let` to initialize a variable named `numbers` and set it to
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `pick` function should initalize `numbers` to an empty array `[]`.
|
||||
Your `pick` function should initialize `numbers` to an empty array `[]`.
|
||||
|
||||
```js
|
||||
assert.match(pick.toString(), /numbers\s*=\s*\[\]/);
|
||||
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
---
|
||||
id: 64e4e4c4ec263b62ae7bf54d
|
||||
title: Step 1
|
||||
challengeType: 0
|
||||
dashedName: step-1
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this project, you will learn how `localStorage` works in JavaScript by building a Todo app. <dfn>LocalStorage</dfn> is a web storage feature of JavaScript that lets you persist data by storing the data as a **key:value** pair.
|
||||
|
||||
The HTML and CSS for this project have been provided for you. Take a look at the files to get yourself familiarized with them.
|
||||
|
||||
Begin by accessing the `task-form`, `confirm-close-dialog`, and `open-task-form-btn` elements with the `getElementById()` method. Save them in the variables `taskForm`, `confirmCloseDialog`, and `openTaskFormBtn`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `getElementById()` to access the `task-form` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)task\-form\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `task-form` element to the variable `taskForm`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskForm\s*=\s*document\.getElementById\(\s*('|"|`)task\-form\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `confirm-close-dialog` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)confirm\-close\-dialog\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `confirm-close-dialog` element to the variable `confirmCloseDialog`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+confirmCloseDialog\s*=\s*document\.getElementById\(\s*('|"|`)confirm\-close\-dialog\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `open-task-form-btn` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)open\-task\-form\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `open-task-form-btn` element to the variable `openTaskFormBtn`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+openTaskFormBtn\s*=\s*document\.getElementById\(\s*('|"|`)open\-task\-form\-btn\1\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
---
|
||||
id: 64e4e6c86954de67a3e44ee3
|
||||
title: Step 2
|
||||
challengeType: 0
|
||||
dashedName: step-2
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You need to access more elements with the `getElementById()` method. This time you need the `close-task-form-btn`, `add-or-update-task-btn`, and `cancel-btn` elements. Save them in the variables `closeTaskFormBtn`, `addOrUpdateTaskBtn`, and `cancelBtn`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `getElementById()` to access the `close-task-form-btn` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)close\-task\-form\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `close-task-form-btn` element to the variable `closeTaskFormBtn`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+closeTaskFormBtn\s*=\s*document\.getElementById\(\s*('|"|`)close\-task\-form\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `add-or-update-task-btn` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)add\-or\-update\-task\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `add-or-update-task-btn` element to the variable `addOrUpdateTaskBtn`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+addOrUpdateTaskBtn\s*=\s*document\.getElementById\(\s*('|"|`)add\-or\-update\-task\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `cancel-btn` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)cancel\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `cancel-btn` element to the variable `cancelBtn`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+cancelBtn\s*=\s*document\.getElementById\(\s*('|"|`)cancel\-btn\1\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
---
|
||||
id: 64e4e6fe78b5aa67ef2fc3e7
|
||||
title: Step 3
|
||||
challengeType: 0
|
||||
dashedName: step-3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Next, access the `discard-btn`, `tasks-container`, and `title-input` elements using the `getElementById()` method. Save them in variables named `discardBtn`, `tasksContainer`, and `titleInput`, respectively.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `getElementById()` to access the `discard-btn` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)discard\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `discard-btn` element to the variable `discardBtn`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+discardBtn\s*=\s*document\.getElementById\(\s*('|"|`)discard\-btn\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `tasks-container` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)tasks\-container\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `tasks-container` element to the variable `tasksContainer`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+tasksContainer\s*=\s*document\.getElementById\(\s*('|"|`)tasks\-container\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `title-input` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)title\-input\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `title-input` element to the variable `titleInput`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+titleInput\s*=\s*document\.getElementById\(\s*('|"|`)title\-input\1\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
---
|
||||
id: 64e4e7241f52bb682eeb8211
|
||||
title: Step 4
|
||||
challengeType: 0
|
||||
dashedName: step-4
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The last set of elements you need to get from the HTML file are the `date-input` and `description-input` elements. Save them in the variables `dateInput` and `descriptionInput` respectively.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `getElementById()` to access the `date-input` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)date\-input\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `date-input` element to the variable `dateInput`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+dateInput\s*=\s*document\.getElementById\(\s*('|"|`)date\-input\1\);?/)
|
||||
```
|
||||
|
||||
You should use `getElementById()` to access the `description-input` element.
|
||||
|
||||
```js
|
||||
assert.match(code, /document\.getElementById\(\s*('|"|`)description\-input\1\);?/)
|
||||
```
|
||||
|
||||
You should assign the `description-input` element to the variable `descriptionInput`. Don't forget to use `const` to declare the variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+descriptionInput\s*=\s*document\.getElementById\(\s*('|"|`)description\-input\1\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
---
|
||||
id: 64e4e74d0fb4f0687bf4145d
|
||||
title: Step 5
|
||||
challengeType: 0
|
||||
dashedName: step-5
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Create a `taskData` constant and set it to an empty array. This array will store all the tasks along with their associated data, including title, due date, and description. This storage will enable you to keep track of tasks, display them on the page, and save them to `localStorage`.
|
||||
|
||||
Use `let` to create a `currentTask` variable and set it to an empty object. This variable will be used to track the state when editing and discarding tasks.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a `taskData` constant set to an empty array.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskData\s*=\s*\[\];?/)
|
||||
```
|
||||
|
||||
You should use `let` to create an empty `currentTask` object.
|
||||
|
||||
```js
|
||||
assert.match(code, /let\s+currentTask\s*=\s*\{\};?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
---
|
||||
id: 64e4e78a7ea4a168de4e6a38
|
||||
title: Step 6
|
||||
challengeType: 0
|
||||
dashedName: step-6
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now, you will work on opening and closing the form modal.
|
||||
|
||||
Add a `click` event listener to the `openTaskFormBtn` variable, and then use `classList` to toggle the `hidden` class on the `taskForm` element.
|
||||
|
||||
Now you can click on the "Add new Task" button and see the form modal.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should call the `addEventListener()` method on your `openTaskFormBtn` variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /openTaskFormBtn\.addEventListener\(/)
|
||||
```
|
||||
|
||||
Your event listener should listen for a `click` event.
|
||||
|
||||
```js
|
||||
assert.match(code, /openTaskFormBtn\.addEventListener\(('|"|`)click\1/)
|
||||
```
|
||||
|
||||
Your event listener should use arrow syntax, then use `classList` property and it's `toggle` method on the `taskForm`, to toggle the class `hidden`. Don't use curly braces.
|
||||
|
||||
```js
|
||||
assert.match(code, /openTaskFormBtn\.addEventListener\(('|"|`)click\1\,\s*\(\)\s*\s*=>\s*taskForm\.classList\.toggle\(\1hidden\1\)\s*\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
---
|
||||
id: 64e4e7bbedb22d6939001ad3
|
||||
title: Step 7
|
||||
challengeType: 0
|
||||
dashedName: step-7
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Add a `click` event listener to the `closeTaskFormBtn` variable, then use the `showModal()` method on your `confirmCloseDialog` variable. This will display a modal with the `Discard` and `Cancel` buttons.
|
||||
|
||||
`showModal()` is a method associated with the HTML `dialog` element. It is used to display a modal dialog box on a web page.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should call the `addEventListener()` method on your `closeTaskFormBtn` variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /closeTaskFormBtn\.addEventListener\(/)
|
||||
```
|
||||
|
||||
Your event listener should listen for a `click` event.
|
||||
|
||||
```js
|
||||
assert.match(code, /closeTaskFormBtn\.addEventListener\(('|"|`)click\1/)
|
||||
```
|
||||
|
||||
Your event listener should use arrow syntax to set the `showModal()` method on `confirmCloseDialog`.
|
||||
|
||||
```js
|
||||
assert.match(code, /closeTaskFormBtn\.addEventListener\(["'`]click["'`],\s*\(.*\)\s*=>\s*{?\s*confirmCloseDialog\.showModal\(\);?\s*}?\s*\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
---
|
||||
id: 64e4eaaa9070a66aecbfe603
|
||||
title: Step 8
|
||||
challengeType: 0
|
||||
dashedName: step-8
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
If the user clicks the `Cancel` button, you want to cancel the process (close the modal showing the two buttons) so the user can continue editing.
|
||||
|
||||
Add a `click` event listener to the `cancelBtn` element, then use the `close()` method on the `confirmCloseDialog` variable.
|
||||
|
||||
`close()` is a method of the window object you can use to close the current window, or a modal you create with the `dialog` element.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should call the `addEventListener()` method on your `cancelBtn` variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /cancelBtn\.addEventListener\(/)
|
||||
```
|
||||
|
||||
Your event listener should listen for a `click` event.
|
||||
|
||||
```js
|
||||
assert.match(code, /cancelBtn\.addEventListener\(('|"|`)click\1/)
|
||||
```
|
||||
|
||||
Your event listener should use arrow syntax to set the `close()` method on `confirmCloseDialog`. Don't use curly braces.
|
||||
|
||||
```js
|
||||
assert.match(code, /cancelBtn\.addEventListener\(('|"|`)click\1\,\s*\(\)\s*\s*=>\s*confirmCloseDialog\.close\(\)\s*\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
```
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
---
|
||||
id: 64e4ebc7eabc5a6babd479cd
|
||||
title: Step 9
|
||||
challengeType: 0
|
||||
dashedName: step-9
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
If the user clicks the `Discard` button, you want to close the modal showing the `Cancel` and `Discard` buttons, then hide the form modal.
|
||||
|
||||
Add a click event listener to `discardBtn`, then use the `close()` method on the `confirmCloseDialog` variable. Also, use `classList` to toggle the class `hidden` on `taskForm` so the form modal will close too.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should call the `addEventListener()` method on your `discardBtn` variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /discardBtn\.addEventListener\(/)
|
||||
```
|
||||
|
||||
Your event listener should listen for a `click` event.
|
||||
|
||||
```js
|
||||
assert.match(code, /discardBtn\.addEventListener\(('|"|`)click\1/)
|
||||
```
|
||||
|
||||
You should use arrow syntax to set your event listener to an empty pair of curly braces.
|
||||
|
||||
```js
|
||||
assert.match(code, /discardBtn\.addEventListener\(('|"|`)click\1,\s*\(\)\s*=>\s*\{/)
|
||||
```
|
||||
|
||||
Your event listener should use the `close()` method on `confirmCloseDialog`.
|
||||
|
||||
```js
|
||||
assert.match(code, /discardBtn\.addEventListener\(('|"|`)click\1,\s*\(\)\s*=>\s*\{\s*confirmCloseDialog\.close\(\);?/)
|
||||
```
|
||||
|
||||
Your event listener should use `classList` to toggle the class `hidden` on `taskForm`.
|
||||
|
||||
```js
|
||||
assert.match(code, /discardBtn\.addEventListener\(('|"|`)click\1,\s*\(\)\s*=>\s*\{\s*confirmCloseDialog\.close\(\);?\s*taskForm\.classList\.toggle\(\1hidden\1\);?\s*\}\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
---
|
||||
id: 64e4ecd7735a566c9266a338
|
||||
title: Step 10
|
||||
challengeType: 0
|
||||
dashedName: step-10
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now that you've worked on opening and closing the modal, it's time to get the values from the input fields, save them into the `taskData` array, and display them on the page.
|
||||
|
||||
To start, add a `submit` event listener to your `taskForm` element and pass in `e` as the parameter of your arrow function. Inside the curly braces, use the `preventDefault()` method to stop the browser from refreshing the page after submitting the form.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should call the `addEventListener()` method on your `taskForm` variable.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskForm\.addEventListener\(/)
|
||||
```
|
||||
|
||||
Your event listener should listen for a `submit` event.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskForm\.addEventListener\(('|"|`)submit\1/)
|
||||
```
|
||||
|
||||
You should use arrow syntax to set your event listener to an empty pair of curly braces with `e` as the parameter.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskForm\.addEventListener\(('|"|`)submit\1,\s*\(e\)\s*=>\s*\{/)
|
||||
```
|
||||
|
||||
You should use the `e.preventDefault()` method to stop the browser from reloading the page.
|
||||
|
||||
```js
|
||||
assert.match(code, /taskForm\.addEventListener\(('|"|`)submit\1,\s*\(e\)\s*=>\s*\{\s*e\.preventDefault\(\);?\s*\}\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
+337
@@ -0,0 +1,337 @@
|
||||
---
|
||||
id: 64e4eec13546c06d61a63d59
|
||||
title: Step 11
|
||||
challengeType: 0
|
||||
dashedName: step-11
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You need to determine whether the task being added already exists or not. If it doesn't exist, you will add it, and if it already exists, you will set it up for editing. You can use the `findIndex()` method to accomplish this.
|
||||
|
||||
`findIndex` is an array method that lets find the index of the first element in an array that satisfies a given testing function.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```js
|
||||
const numbers = [3, 1, 5, 6, 10, 9, 8];
|
||||
const firstEvenNumIndex = numbers.findIndex((num) => num % 2 === 0);
|
||||
|
||||
console.log(firstEvenNumIndex); // Output: 3 – because the first even number (6) is at index 3
|
||||
```
|
||||
|
||||
Declare a `dataArrIndex` variable using `const` and set it to the result of the `findIndex()` method applied to the `taskData` array. Utilize arrow syntax to provide a callback function with `item` as the parameter, and within the callback, check if the `id` property of `item` is equal to the `id` property of `currentTask`.
|
||||
|
||||
If the task exists, this returns the index, and if it doesn't exist, it returns `-1`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` to declare a `dataArrIndex` variable and set it to `taskData.findIndex()`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const dataArrIndex\s*=\s*taskData\.findIndex\(/)
|
||||
```
|
||||
|
||||
You should pass in `item` as the parameter of the arrow function callback. Don't use curly braces.
|
||||
|
||||
```js
|
||||
assert.match(code, /const dataArrIndex\s*=\s*taskData\.findIndex\(\(?item\)?/)
|
||||
```
|
||||
|
||||
Your arrow function callback should check if `item.id === currentTask.id`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const dataArrIndex\s*=\s*taskData\.findIndex\(\(?item\)?\s*=>\s*item.id\s*===\s*currentTask.id\s*\);?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
});
|
||||
```
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
---
|
||||
id: 64e4f01d6c72086e016a8626
|
||||
title: Step 12
|
||||
challengeType: 0
|
||||
dashedName: step-12
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Next, retrieve the values from the input fields and store them in a `taskObj` object. Each task should also have a unique `id`.
|
||||
|
||||
Create a `taskObj` object with an `id` property as the first property. For the value of the `id` property, retrieve the value of the `titleInput` field, convert it to lowercase, and then use the `split()` and `join()` methods to hyphenate it.
|
||||
|
||||
Make sure all of those are in template literals because you need the `id` property value as a string.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should use `const` to create a `taskObj` object.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskObj\s*=\s*\{\s*/)
|
||||
```
|
||||
|
||||
Your `taskObj` object should have a key of `id`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskObj\s*=\s*\{\s*id/)
|
||||
```
|
||||
|
||||
You should use template strings to get `titleInput.value` as the value of your `id` key and convert it to lowercase.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskObj\s*=\s*\{\s*id:\s*`\$\{titleInput\.value\.toLowerCase\(\)/)
|
||||
```
|
||||
|
||||
You should use `.split(' ')` on `titleInput.value.toLowerCase()`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskObj\s*=\s*\{\s*id:\s*`\$\{titleInput\.value\.toLowerCase\(\)\.split\(('|")\s{1}\1\)/)
|
||||
```
|
||||
|
||||
You should use `.join('-')` on `titleInput.value.toLowerCase().split(' ')`.
|
||||
|
||||
```js
|
||||
assert.match(code, /const\s+taskObj\s*=\s*\{\s*id:\s*`\$\{titleInput\.value\.toLowerCase\(\)\.split\(('|")\s{1}\1\)\.join\(\1-\1\)\}`,?\s*\}/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
});
|
||||
```
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
---
|
||||
id: 64ec89ee549ecf802de2b3e2
|
||||
title: Step 14
|
||||
challengeType: 0
|
||||
dashedName: step-14
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Retrieve the values from the `titleInput`, `dateInput`, and `descriptionInput` fields, and then save them in the properties `title`, `date`, and `description` of the `taskObj` object.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should get the value of `titleInput` and save it in a `title` key.
|
||||
|
||||
```js
|
||||
assert.match(code, /title:\s*titleInput\.value,/)
|
||||
```
|
||||
|
||||
You should get the value of `dateInput` and save it in a `date` key.
|
||||
|
||||
```js
|
||||
assert.match(code, /date:\s*dateInput\.value,/)
|
||||
```
|
||||
|
||||
You should get the value of `descriptionInput` and save it in a `description` key.
|
||||
|
||||
```js
|
||||
assert.match(code, /description:\s*descriptionInput\.value,?/)
|
||||
```
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
};
|
||||
});
|
||||
```
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
---
|
||||
id: 64ec8f717b261e824d82d6a5
|
||||
title: Step 15
|
||||
challengeType: 0
|
||||
dashedName: step-15
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now that you have obtained the values from the input fields and generated an `id`, you want to add them to your `taskData` array to keep track of each task. However, you should only do this if the task is new. If the task already exists, you will set it up for editing. This is why you have the `dataArrIndex` variable, which provides the index of each task.
|
||||
|
||||
Create an `if` statement with the condition `dataArrIndex === -1`. Within the `if` statement, use the `unshift()` method to add the `taskObj` object to the beginning of the `taskData` array.
|
||||
|
||||
`unshift()` is an array method that is used to add one or more elements to the beginning of an array.
|
||||
|
||||
**N.B:** Try adding a task and log `taskData` to the console to see what it looks like.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create an `if` statement with the condition `dataArrIndex === -1`.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(dataArrIndex\s*===\s*-1\)\s*\{/)
|
||||
```
|
||||
|
||||
Your `if` statement should have `taskData.unshift(taskObj)` in it's body.
|
||||
|
||||
```js
|
||||
assert.match(code, /if\s*\(dataArrIndex\s*===\s*-1\)\s*\{\s*taskData\.unshift\(taskObj\);?\s*\}/)
|
||||
```
|
||||
|
||||
|
||||
# --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>Learn localStorage By Building a Todo App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Todo App</h1>
|
||||
<div class="todo-app">
|
||||
<button id="open-task-form-btn" class="btn large-btn">
|
||||
Add New Task
|
||||
</button>
|
||||
<form class="task-form hidden" id="task-form">
|
||||
<div class="task-form-header">
|
||||
<button id="close-task-form-btn" class="close-task-form-btn" type="button" aria-label="close">
|
||||
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#F44336" d="M21.5 4.5H26.501V43.5H21.5z" transform="rotate(45.001 24 24)" /><path fill="#F44336" d="M21.5 4.5H26.5V43.501H21.5z" transform="rotate(135.008 24 24)" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-form-body">
|
||||
<label class="task-form-label" for="title-input">Title</label>
|
||||
<input required type="text" class="form-control" id="title-input" value="" />
|
||||
<label class="task-form-label" for="date-input">Date</label>
|
||||
<input type="date" class="form-control" id="date-input" value="" />
|
||||
<label class="task-form-label" for="description-input">Description</label>
|
||||
<textarea class="form-control" id="description-input" cols="30" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="task-form-footer">
|
||||
<button id="add-or-update-task-btn" class="btn large-btn" type="submit" aria-label="add task">
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<dialog id="confirm-close-dialog">
|
||||
<form method="dialog">
|
||||
<p class="discard-message-text">Discard unsaved changes?</p>
|
||||
<div class="confirm-close-dialog-btn-container">
|
||||
<button id="cancel-btn" class="btn" aria-label="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="discard-btn" class="btn" aria-label="discard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div id="tasks-container"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light-grey: #f5f6f7;
|
||||
--dark-grey: #0a0a23;
|
||||
--yellow: #f1be32;
|
||||
--golden-yellow: #feac32;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--dark-grey);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-grey);
|
||||
margin: 20px 0 40px 0;
|
||||
}
|
||||
|
||||
.todo-app {
|
||||
background-color: var(--white);
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
border: 5px solid var(--yellow);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-image: linear-gradient(#ffcc4c, #f89808);
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 80%;
|
||||
font-size: 1.2rem;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.close-task-form-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--white);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
height: 350px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.task-form-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.task-form-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-form-label,
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#confirm-close-dialog {
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.confirm-close-dialog-btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.discard-message-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#tasks-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.todo-app,
|
||||
.task-form {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.task-form-label {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#title-input,
|
||||
#date-input,
|
||||
#description-input {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const confirmCloseDialog = document.getElementById("confirm-close-dialog");
|
||||
const openTaskFormBtn = document.getElementById("open-task-form-btn");
|
||||
const closeTaskFormBtn = document.getElementById("close-task-form-btn");
|
||||
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
|
||||
const cancelBtn = document.getElementById("cancel-btn");
|
||||
const discardBtn = document.getElementById("discard-btn");
|
||||
const tasksContainer = document.getElementById("tasks-container");
|
||||
const titleInput = document.getElementById("title-input");
|
||||
const dateInput = document.getElementById("date-input");
|
||||
const descriptionInput = document.getElementById("description-input");
|
||||
|
||||
const taskData = [];
|
||||
let currentTask = {};
|
||||
|
||||
openTaskFormBtn.addEventListener("click", () =>
|
||||
taskForm.classList.toggle("hidden")
|
||||
);
|
||||
|
||||
closeTaskFormBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.showModal();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
|
||||
|
||||
discardBtn.addEventListener("click", () => {
|
||||
confirmCloseDialog.close();
|
||||
taskForm.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
|
||||
taskForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
|
||||
const taskObj = {
|
||||
id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
|
||||
title: titleInput.value,
|
||||
date: dateInput.value,
|
||||
description: descriptionInput.value,
|
||||
};
|
||||
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
});
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user