feat(curriculum): adding JS A11y note taking app (#61449)

Co-authored-by: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com>
This commit is contained in:
Jessica Wilkins
2025-07-25 02:49:49 -07:00
committed by GitHub
parent 35bfcdebf7
commit 3ee3a928a9
17 changed files with 1728 additions and 0 deletions
+7
View File
@@ -3468,6 +3468,13 @@
"In this workshop, you will build a dynamic tabbed interface that showcases facts about the planets in the solar system."
]
},
"workshop-note-taking-app": {
"title": "Build a Note Taking App",
"intro": [
"In this workshop, you are going to build an accessible note taking app.",
"This will provide you with the opportunity to practice working with <code>aria-live</code> attribute."
]
},
"review-js-a11y": {
"title": "JavaScript and Accessibility Review",
"intro": [
@@ -0,0 +1,9 @@
---
title: Introduction to Build a Note Taking App
block: workshop-note-taking-app
superBlock: full-stack-developer
---
## Introduction to Build a Note Taking App
In this workshop, you are going to build an accessible note taking app. This will provide you with the opportunity to practice working with `aria-live` attribute.
@@ -0,0 +1,65 @@
{
"name": "Build a Note Taking App",
"blockType": "workshop",
"blockLayout": "challenge-grid",
"isUpcomingChange": true,
"dashedName": "workshop-note-taking-app",
"superBlock": "full-stack-developer",
"helpCategory": "JavaScript",
"challengeOrder": [
{
"id": "68813c1b28ec3fd41838daf6",
"title": "Step 1"
},
{
"id": "68813f8b1b11bbdda98c6214",
"title": "Step 2"
},
{
"id": "68813fbfc9f56dde3f21da08",
"title": "Step 3"
},
{
"id": "688140eac18479dee90553e5",
"title": "Step 4"
},
{
"id": "6881417fdb4032df98134a89",
"title": "Step 5"
},
{
"id": "688141a8be6e6be03af945ea",
"title": "Step 6"
},
{
"id": "688141e05e367de0e3ef7635",
"title": "Step 7"
},
{
"id": "68814221fc4752e17b9696bd",
"title": "Step 8"
},
{
"id": "6881424b87c826e21247645b",
"title": "Step 9"
},
{
"id": "688142a607d1dce2aa82da77",
"title": "Step 10"
},
{
"id": "6881431e481bf6e352a33969",
"title": "Step 11"
},
{
"id": "6881436e1e2afae400e8b4fe",
"title": "Step 12"
},
{
"id": "688143c66d5665e4b3409977",
"title": "Step 13"
}
],
"usesMultifileEditor": true,
"hasEditableBoundaries": true
}
@@ -0,0 +1,111 @@
---
id: 68813c1b28ec3fd41838daf6
title: Step 1
challengeType: 0
dashedName: step-1
demoType: onLoad
---
# --description--
In this workshop, you will practice working with the `aria-live` and `aria-label` attributes by building an accessible note taking app.
Most of the HTML and all of the CSS has been provided for you. The first few steps will involve adding the remaining markup.
If you look at the preview, there is text to tell you to click on the card to edit it. But if you try to do that, it will not work. To make that `div` element editable, you can use the `contenteditable` attribute like this:
```html
<div contenteditable="true"></div>
```
In the opening `div` tag, add the `contenteditable` attribute and set its value to `"true"`. Now you should be able to click on the element and edit the text.
# --hints--
Your opening `div` tag should have the `contenteditable` attribute.
```js
assert.isTrue(document.getElementById("note").hasAttribute("contenteditable"));
```
Your `div` element should have the `contenteditable` attribute set to `"true"`.
```js
assert.equal(document.getElementById("note").getAttribute("contenteditable"), "true");
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
--fcc-editable-region--
<div id="note" class="note">
--fcc-editable-region--
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
@@ -0,0 +1,102 @@
---
id: 68813f8b1b11bbdda98c6214
title: Step 2
challengeType: 0
dashedName: step-2
---
# --description--
Since a `div` element is being used for the note, screen readers will not understand its purpose. You can fix that by adding an `aria-label` attribute. This will help with accessibility and provide meaning for this `div` element.
In your opening `div` tag, add an `aria-label` attribute and set its value to `"Note editor"`.
# --hints--
Your opening `div` tag should have the `aria-label` attribute.
```js
assert.isTrue(document.getElementById("note").hasAttribute("aria-label"));
```
Your opening `div` tag should have the `aria-label` attribute set to `"Note editor"`.
```js
assert.equal(document.getElementById("note").getAttribute("aria-label"), "Note editor");
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
--fcc-editable-region--
<div id="note" class="note" contenteditable="true">
--fcc-editable-region--
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
@@ -0,0 +1,110 @@
---
id: 68813fbfc9f56dde3f21da08
title: Step 3
challengeType: 0
dashedName: step-3
---
# --description--
When a user edits a note and either tabs or clicks outside of the card, then a status message should display notifying users that the note was saved successfully.
To begin, create a `div` element with an `id` attribute set to `"status"`.
# --hints--
You should have a second `div` element.
```js
assert.lengthOf(document.querySelectorAll("div"), 2);
```
Your second `div` element should have an `id` attribute.
```js
assert.isTrue(document.querySelectorAll("div")[1].hasAttribute("id"));
```
Your second `div` element should have an `id` attribute set to `"status"`.
```js
assert.equal(document.querySelectorAll("div")[1].getAttribute("id"), "status");
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
--fcc-editable-region--
--fcc-editable-region--
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
@@ -0,0 +1,108 @@
---
id: 688140eac18479dee90553e5
title: Step 4
challengeType: 0
dashedName: step-4
---
# --description--
Since you are using another `div` element, you will need to make this more accessible for screen readers.
In a prior lecture, you learned about the `aria-live` attribute which is used to create a live region on a page. This will allow screen reader users to be automatically notified when the content of the live region changes, without needing to manually focus on or interact with it.
In your `#status` element, add an `aria-live` attribute and set its value to `"polite"`.
The `polite` value will tell screen readers to wait until any current announcement is finished or until the user stops typing before announcing the update.
# --hints--
Your `#status` element should have the `aria-live` attribute.
```js
assert.isTrue(document.getElementById("status").hasAttribute("aria-live"));
```
Your `#status` element should have the `aria-live` attribute set to `"polite"`.
```js
assert.equal(document.getElementById("status").getAttribute("aria-live"), "polite");
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
--fcc-editable-region--
<div id="status"></div>
--fcc-editable-region--
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
@@ -0,0 +1,110 @@
---
id: 6881417fdb4032df98134a89
title: Step 5
challengeType: 0
dashedName: step-5
---
# --description--
The remaining steps for the workshop will involve adding the code for dynamically showing the notes saved message and logging the current notes to the console.
Start by creating a variable called `noteEl` and assigning it the result of querying the document for the element with the `id` of `note`.
Then create another variable called `statusEl` and assign it the result of querying the document for the element with the `id` of `status`.
# --hints--
You should have a `noteEl` variable and assign it the result of querying the document for the element with the `id` of `note`.
```js
assert.equal(noteEl, document.getElementById("note"));
```
You should have a `statusEl` variable and assign it the result of querying the document for the element with the `id` of `status`.
```js
assert.equal(statusEl, document.getElementById("status"));
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<div id="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
```js
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,119 @@
---
id: 688141a8be6e6be03af945ea
title: Step 6
challengeType: 0
dashedName: step-6
---
# --description--
When a user edits the note, you want to keep track of the edited version.
Use `let` to create a variable called `currentContent` and assign it an empty string.
*NOTE*: In a real world application you would normally save your notes in a database. Or you could even save them in local storage. However, working with databases and local storage is beyond the scope of this workshop and those concepts will be taught later on.
# --hints--
You should use `let` to create the `currentContent` variable.
```js
assert.match(code, /let\s+currentContent/);
```
Your `currentContent` variable should be a string.
```js
assert.isString(currentContent);
```
Your `currentContent` variable should be initialized with an empty string.
```js
assert.equal(currentContent, "");
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<div id="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
```js
const noteEl = document.getElementById("note");
const statusEl = document.getElementById("status");
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,132 @@
---
id: 688141e05e367de0e3ef7635
title: Step 7
challengeType: 0
dashedName: step-7
---
# --description--
Right now, `currentContent` is initialized with the value of an empty string. But the desired result would be to have `currentContent` hold the value of the current note text.
In an earlier lecture, you learned how to work with the `DOMContentLoaded` event like this:
```js
window.addEventListener("DOMContentLoaded", () => {
// Do stuff here
});
```
When everything in the HTML document has been loaded and parsed, you will be able to access the `noteEl` safely.
Start by attaching an `addEventListener` method to the `window` object. The first argument for the `addEventListener` method should be the `"DOMContentLoaded"` event. The second argument should be an arrow function. Inside the body of the arrow function, reassign `currentContent` to the value of `noteEl.textContent`.
# --hints--
You should attach an `addEventListener` method to the `window` object.
```js
assert.match(code, /window\.addEventListener/);
```
Your event listener should listen for the `"DOMContentLoaded"` event.
```js
assert.match(code, /window\.addEventListener\(['"]DOMContentLoaded['"]\,/);
```
Inside the body of the arrow function, you should reassign `currentContent` to the value of `noteEl.textContent`.
```js
const event = new Event("DOMContentLoaded");
window.dispatchEvent(event);
assert.equal(currentContent, noteEl.textContent);
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<div id="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
```js
const noteEl = document.getElementById("note");
const statusEl = document.getElementById("status");
let currentContent = "";
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,131 @@
---
id: 68814221fc4752e17b9696bd
title: Step 8
challengeType: 0
dashedName: step-8
---
# --description--
When the user clicks on the note, makes edits and then clicks outside of the note, you will need a way to get that newly edited version. You can accomplish this by using the `blur` event. This event fires when an element loses focus. Here is an example:
```js
element.addEventListener("blur", () => {
// do something here
});
```
Attach an `addEventListener` method to the `noteEl` variable. The first argument should be the `"blur"` event and the second argument should an arrow function. Inside of the body of that arrow function, use `const` to create a new variable called `newContent` and assign it the value of `noteEl.innerHTML`.
`innerHTML` is used here to help preserve spacing and formatting for the note.
# --hints--
You should attach an `addEventListener` method to the `noteEl` variable.
```js
assert.match(code, /noteEl\.addEventListener/);
```
Your `addEventListener` method should have a first argument of the `"blur"` event.
```js
assert.match(code, /noteEl\.addEventListener\(['"]blur['"],/);
```
Inside of the body of the arrow function, you should use `const` to create a new variable called `newContent` and assign it the value of `noteEl.innerHTML`.
```js
assert.match(code, /noteEl\.addEventListener\(['"]blur['"],\s*\(\)\s*=>\s*{[^}]*const\s+newContent\s*=\s*noteEl\.innerHTML[^}]*}\)/);
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<div id="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
```js
const noteEl = document.getElementById("note");
const statusEl = document.getElementById("status");
let currentContent = "";
--fcc-editable-region--
--fcc-editable-region--
window.addEventListener("DOMContentLoaded", () => {
currentContent = noteEl.textContent;
});
```
@@ -0,0 +1,114 @@
---
id: 6881424b87c826e21247645b
title: Step 9
challengeType: 0
dashedName: step-9
---
# --description--
Once you get the edited content, that will now be the current content.
Inside of the event listener, assign `newContent` to `currentContent`.
# --hints--
You should assign `newContent` to `currentContent` inside of the event listener.
```js
assert.match(code, /noteEl\.addEventListener\(['"]blur['"],\s*\(\)\s*=>\s*{[^}]*const\s+newContent\s*=\s*noteEl\.innerHTML[^}]*currentContent\s*=\s*newContent[^}]*}\)/);
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<div id="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
```js
const noteEl = document.getElementById("note");
const statusEl = document.getElementById("status");
let currentContent = "";
--fcc-editable-region--
noteEl.addEventListener("blur", () => {
const newContent = noteEl.innerHTML;
});
--fcc-editable-region--
window.addEventListener("DOMContentLoaded", () => {
currentContent = noteEl.textContent;
});
```
@@ -0,0 +1,118 @@
---
id: 688142a607d1dce2aa82da77
title: Step 10
challengeType: 0
dashedName: step-10
---
# --description--
It would be nice to see the edited changes in the console.
Add a `console.log(currentContent);`.
To test out your changes, trying editing the note and then clicking out of the note. You should now see the edited note in the console.
# --hints--
You should add a `console.log(currentContent);` inside of the event listener, after assigning `newContent` to `currentContent`.
```js
assert.match(code, /noteEl\.addEventListener\(['"]blur['"],\s*\(\)\s*=>\s*{[^}]*const\s+newContent\s*=\s*noteEl\.innerHTML[^}]*currentContent\s*=\s*newContent[^}]*console\.log\(currentContent\)[^}]*}\)/);
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<div id="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
```js
const noteEl = document.getElementById("note");
const statusEl = document.getElementById("status");
let currentContent = "";
--fcc-editable-region--
noteEl.addEventListener("blur", () => {
const newContent = noteEl.innerHTML;
currentContent = newContent;
});
--fcc-editable-region--
window.addEventListener("DOMContentLoaded", () => {
currentContent = noteEl.textContent;
});
```
@@ -0,0 +1,122 @@
---
id: 6881431e481bf6e352a33969
title: Step 11
challengeType: 0
dashedName: step-11
---
# --description--
As mentioned earlier, when the user edits the note and then clicks outside of the note, there should be a message to display that the note was saved.
To accomplish this, set the `statusEl.textContent` to the string `"Note saved successfully!"`.
Now when you test it out, you should see that message display on the screen.
# --hints--
When the user edits a note, the status message should be updated to indicate that the note was saved.
```js
const event = new Event("blur");
note.dispatchEvent(event);
assert.equal(statusEl.textContent, "Note saved successfully!");
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<div id="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
```js
const noteEl = document.getElementById("note");
const statusEl = document.getElementById("status");
let currentContent = "";
--fcc-editable-region--
noteEl.addEventListener("blur", () => {
const newContent = noteEl.innerHTML;
currentContent = newContent;
console.log(currentContent);
});
--fcc-editable-region--
window.addEventListener("DOMContentLoaded", () => {
currentContent = noteEl.textContent;
});
```
@@ -0,0 +1,119 @@
---
id: 6881436e1e2afae400e8b4fe
title: Step 12
challengeType: 0
dashedName: step-12
---
# --description--
Right now, anytime the user edits a note and clicks outside of the note, the `currentContent` variable is being updated. But what about situations where the user focuses on the note and decides to leave without making any edits?
Well, in this situation, you can add a condition to handle that use case.
Above your `currentContent = newContent;` line, add an `if` statement to check if `currentContent` is equal to `newContent`. If so, return.
# --hints--
You should add an `if` statement to check if `currentContent` is equal to `newContent`. If so, return.
```js
assert.match(code, /if\s*\(\s*currentContent\s*=={2,3}\s*newContent\s*\)\s*(?:{\s*return;?\s*}|return\s*;?)/);
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<div id="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
```js
const noteEl = document.getElementById("note");
const statusEl = document.getElementById("status");
let currentContent = "";
noteEl.addEventListener("blur", () => {
const newContent = noteEl.innerHTML;
--fcc-editable-region--
currentContent = newContent;
console.log(currentContent);
--fcc-editable-region--
statusEl.textContent = "Note saved successfully!";
});
window.addEventListener("DOMContentLoaded", () => {
currentContent = noteEl.textContent;
});
```
@@ -0,0 +1,248 @@
---
id: 688143c66d5665e4b3409977
title: Step 13
challengeType: 0
dashedName: step-13
---
# --description--
Things seem to be working except for one small issue. If you edit a note and then leave that note, you should see the `"Note saved successfully!"` message. But if you try to edit the note again, the message is still displaying.
The desired behavior is for the message to disappear when the user focuses on the element. You can use the `focus` event for this.
```js
element.addEventListener("focus", () => {
// do something here
});
```
Attach an `addEventListener` to the `noteEl` variable. The first argument should be the `"focus"` event and the second argument should be a callback function. Inside of that callback function, set the `statusEl.textContent` to an empty string. This will reset the live region when the note is focused.
And with that last change, your workshop is complete!
# --hints--
When the user focuses on the note, you should set the `statusEl.textContent` to an empty string.
```js
window.dispatchEvent(new Event("DOMContentLoaded"));
const temp = console.log;
console.log = () => {};
noteEl.innerHTML = "New content";
noteEl.dispatchEvent(new Event("blur"));
assert.equal(noteEl.innerHTML, "New content");
assert.equal(statusEl.textContent, "Note saved successfully!");
noteEl.dispatchEvent(new Event("focus"));
assert.equal(statusEl.textContent, "");
```
# --seed--
## --seed-contents--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<div id="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
```js
const noteEl = document.getElementById("note");
const statusEl = document.getElementById("status");
let currentContent = "";
--fcc-editable-region--
--fcc-editable-region--
noteEl.addEventListener("blur", () => {
const newContent = noteEl.innerHTML;
if (currentContent === newContent) {
return;
}
currentContent = newContent;
console.log(currentContent);
statusEl.textContent = "Note saved successfully!";
});
window.addEventListener("DOMContentLoaded", () => {
currentContent = noteEl.textContent;
});
```
# --solutions--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Note taking app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<p class="helper-text">Click or tap on the card to edit your note.</p>
<div id="note" class="note" contenteditable="true" aria-label="Note editor">
Many languages have words that carry meanings so specific or culturally rooted that they can't be neatly translated into English.
One example is the Japanese word "tsundoku", which refers to the habit of acquiring books and letting them pile up unread, something many book lovers can relate to. Another is the Portuguese word "saudade", describing a deep, bittersweet longing for something or someone that is absent. Meanwhile, the French word "Dépaysement" captures the disorienting yet exciting feeling of being in a new place, far from home.
These unique words remind us that language is more than vocabulary: it's a window into the values, habits, and emotions of the cultures that create it.
</div>
<div id="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>
```
```css
body {
font-family: Arial, sans-serif;
margin: 2em;
max-width: 700px;
background-color: #f5f5f5;
}
.note {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 1.5em;
margin-bottom: 1em;
line-height: 1.5;
min-height: 250px;
font-size: 16px;
/* This is needed to preserve line breaks in the div */
white-space: pre-wrap;
}
.note[contenteditable="true"] {
caret-color: black;
}
.note:hover {
background-color: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.helper-text {
font-size: 0.9rem;
color: #666;
margin-top: 0.5em;
user-select: none;
font-style: italic;
}
#status {
color: #00471b;
padding: 0 1em;
}
```
```js
const noteEl = document.getElementById("note");
const statusEl = document.getElementById("status");
let currentContent = "";
noteEl.addEventListener("focus", () => {
statusEl.textContent = '';
})
noteEl.addEventListener("blur", () => {
const newContent = noteEl.innerHTML;
if (currentContent === newContent) {
return;
}
currentContent = newContent;
console.log(currentContent);
statusEl.textContent = "Note saved successfully!";
});
window.addEventListener("DOMContentLoaded", () => {
currentContent = noteEl.textContent;
});
```
@@ -834,6 +834,9 @@
{
"dashedName": "workshop-planets-tablist"
},
{
"dashedName": "workshop-note-taking-app"
},
{
"dashedName": "review-js-a11y"
},