chore: import upstream snapshot with attribution
i18n - Build Validation / Validate i18n Builds (24) (push) Has been cancelled
CI - Node.js / Lint (24) (push) Has been cancelled
CI - Node.js / Build (24) (push) Has been cancelled
CI - Node.js / Test (24) (push) Has been cancelled
CI - Node.js / Test - Upcoming Changes (24) (push) Has been cancelled
CI - Node.js / Test - i18n (italian, 24) (push) Has been cancelled
CI - Node.js / Test - i18n (portuguese, 24) (push) Has been cancelled
CD - Docker - GHCR Images / Build and Push Images (push) Has been cancelled
i18n - Build Validation / Validate i18n Builds (24) (push) Has been cancelled
CI - Node.js / Lint (24) (push) Has been cancelled
CI - Node.js / Build (24) (push) Has been cancelled
CI - Node.js / Test (24) (push) Has been cancelled
CI - Node.js / Test - Upcoming Changes (24) (push) Has been cancelled
CI - Node.js / Test - i18n (italian, 24) (push) Has been cancelled
CI - Node.js / Test - i18n (portuguese, 24) (push) Has been cancelled
CD - Docker - GHCR Images / Build and Push Images (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
default: true # include all rules, with exceptions below
|
||||
MD002: false # first heading should not be a top level heading
|
||||
MD013: false # lines can be any length
|
||||
MD022: false # headings don't need surrounding by newlines
|
||||
MD024: false # no duplicate headers
|
||||
MD025: false # headings are used as markers by the parser
|
||||
MD031: true # fenced blocks do need surrounding by newlines
|
||||
MD033: false # inline html is required
|
||||
MD040: true # fenced code blocks should have a language specified
|
||||
MD034: false # allow bare-URLs
|
||||
MD036: false # TODO: **Example** is the main offender, should that be a heading?
|
||||
MD058: false # allow blanks around tables
|
||||
MD060: false # disable table column style
|
||||
whitespace: false # extra whitespace is ignored, so we don't enforce it.
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 647e1f4e8acb466c97ccbedf
|
||||
title: Add Looping Logic to Your Code Using the do-while and while Statements in C#
|
||||
challengeType: 19
|
||||
dashedName: add-looping-logic-to-your-code-using-the-do-while-and-while-statements-in-c-sharp
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
This challenge will be partially completed on the Microsoft Learn platform. Follow these instructions to complete the challenge:
|
||||
|
||||
1. Go to <a href="https://learn.microsoft.com/training/modules/csharp-do-while/" target="_blank" rel="noreferrer">https://learn.microsoft.com/training/modules/csharp-do-while/</a> and complete all the tasks for the "Add Looping Logic to Your Code Using the do-while and while Statements in C#" module. This is **required** to earn the "Add Logic to C# Console Applications" trophy on Microsoft Learn, and qualify for the certification exam.
|
||||
1. When you are finished, come back and correctly answer the question below.
|
||||
|
||||
# --assignment--
|
||||
|
||||
Complete the <a href="https://learn.microsoft.com/training/modules/csharp-do-while/" target="_blank" rel="noreferrer">Add Looping Logic to Your Code Using the `do`-`while` and `while` Statements in C#</a> module on Microsoft Learn. Then, answer the question below.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
A developer needs to iterate through an array of items to find one that matches a certain criteria. Which is the best iteration statement for this purpose?
|
||||
|
||||
## --answers--
|
||||
|
||||
`foreach`
|
||||
|
||||
---
|
||||
|
||||
`for`
|
||||
|
||||
---
|
||||
|
||||
`while`
|
||||
|
||||
## --video-solution--
|
||||
|
||||
1
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 647e214b8acb466c97ccbee0
|
||||
title: Branch the Flow of Code Using the switch-case Construct in C#
|
||||
challengeType: 19
|
||||
dashedName: branch-the-flow-of-code-using-the-switch-case-construct-in-c-sharp
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
This challenge will be partially completed on the Microsoft Learn platform. Follow these instructions to complete the challenge:
|
||||
|
||||
1. Go to <a href="https://learn.microsoft.com/training/modules/csharp-switch-case/" target="_blank" rel="noreferrer">https://learn.microsoft.com/training/modules/csharp-switch-case/</a> and complete all the tasks for the "Branch the Flow of Code Using the switch-case Construct in C#" module. This is **required** to earn the "Add Logic to C# Console Applications" trophy on Microsoft Learn, and qualify for the certification exam.
|
||||
1. When you are finished, come back and correctly answer the question below.
|
||||
|
||||
# --assignment--
|
||||
|
||||
Complete the <a href="https://learn.microsoft.com/training/modules/csharp-switch-case/" target="_blank" rel="noreferrer">Branch the Flow of Code Using the `switch`-`case` Construct in C#</a> module on Microsoft Learn. Then, answer the question below.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What is the purpose of the `break` keyword?
|
||||
|
||||
## --answers--
|
||||
|
||||
The `break` keyword tells the runtime to continue evaluating other cases in the `switch` construct.
|
||||
|
||||
---
|
||||
|
||||
The `break` keyword tells the runtime to stop evaluating other cases in the `switch` construct.
|
||||
|
||||
---
|
||||
|
||||
The `break` keyword tells the runtime to exit the application.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
2
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 647e21598acb466c97ccbee1
|
||||
title: Challenge Project - Develop Branching and Looping Structures in C#
|
||||
challengeType: 19
|
||||
dashedName: challenge-project-develop-branching-and-looping-structures-in-c-sharp
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
This challenge will be partially completed on the Microsoft Learn platform. Follow these instructions to complete the challenge:
|
||||
|
||||
1. Go to <a href="https://learn.microsoft.com/training/modules/challenge-project-develop-branching-looping-structures-c-sharp/" target="_blank" rel="noreferrer">https://learn.microsoft.com/training/modules/challenge-project-develop-branching-looping-structures-c-sharp/</a> and complete all the tasks for the "Challenge Project - Develop Branching and Looping Structures in C#" module. This is **required** to earn the "Add Logic to C# Console Applications" trophy on Microsoft Learn, and qualify for the certification exam.
|
||||
1. When you are finished, come back and correctly answer the question below.
|
||||
|
||||
# --assignment--
|
||||
|
||||
Complete the <a href="https://learn.microsoft.com/training/modules/challenge-project-develop-branching-looping-structures-c-sharp/" target="_blank" rel="noreferrer">Develop Branching and Looping Structures in C#</a> challenge project on Microsoft Learn. Then, answer the question below.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
A developer needs to create an iteration statement. Under what condition is a `while` statement a better choice than a `do` statement?
|
||||
|
||||
## --answers--
|
||||
|
||||
When the expression values evaluated by the iteration statement are unknown prior to the iteration code block.
|
||||
|
||||
---
|
||||
|
||||
When the expression values evaluated by the iteration statement are known prior to the iteration code block.
|
||||
|
||||
---
|
||||
|
||||
When the developer creates nested `do` or `while` loops.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
2
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 647e21658acb466c97ccbee2
|
||||
title: Control Variable Scope and Logic Using Code Blocks in C#
|
||||
challengeType: 19
|
||||
dashedName: control-variable-scope-and-logic-using-code-blocks-in-c-sharp
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
This challenge will be partially completed on the Microsoft Learn platform. Follow these instructions to complete the challenge:
|
||||
|
||||
1. Go to <a href="https://learn.microsoft.com/training/modules/csharp-code-blocks/" target="_blank" rel="noreferrer">https://learn.microsoft.com/training/modules/csharp-code-blocks/</a> and complete all the tasks for the "Control Variable Scope and Logic Using Code Blocks in C#" module. This is **required** to earn the "Add Logic to C# Console Applications" trophy on Microsoft Learn, and qualify for the certification exam.
|
||||
1. When you are finished, come back and correctly answer the question below.
|
||||
|
||||
# --assignment--
|
||||
|
||||
Complete the <a href="https://learn.microsoft.com/training/modules/csharp-code-blocks/" target="_blank" rel="noreferrer">Control Variable Scope and Logic Using Code Blocks in C#</a> module on Microsoft Learn. Then, answer the question below.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Which of the following statements is true about showing/removing the curly braces for code blocks associated with an `if` statement?
|
||||
|
||||
## --answers--
|
||||
|
||||
The curly braces can't be removed from the code block for `else if` and `else` statements.
|
||||
|
||||
---
|
||||
|
||||
If the curly braces are removed from the code blocks of an `if-elseif-else`, the white space must also be removed.
|
||||
|
||||
---
|
||||
|
||||
Always choose a style that improves readability.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 647e21708acb466c97ccbee3
|
||||
title: Evaluate Boolean Expressions to Make Decisions in C#
|
||||
challengeType: 19
|
||||
dashedName: evaluate-boolean-expressions-to-make-decisions-in-c-sharp
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
This challenge will be partially completed on the Microsoft Learn platform. Follow these instructions to complete the challenge:
|
||||
|
||||
1. Go to <a href="https://learn.microsoft.com/training/modules/csharp-evaluate-boolean-expressions/" target="_blank" rel="noreferrer">https://learn.microsoft.com/training/modules/csharp-evaluate-boolean-expressions/</a> and complete all the tasks for the "Evaluate Boolean Expressions to Make Decisions in C#" module. This is **required** to earn the "Add Logic to C# Console Applications" trophy on Microsoft Learn, and qualify for the certification exam.
|
||||
1. When you are finished, come back and correctly answer the question below.
|
||||
|
||||
# --assignment--
|
||||
|
||||
Complete the <a href="https://learn.microsoft.com/training/modules/csharp-evaluate-boolean-expressions/" target="_blank" rel="noreferrer">Evaluate Boolean Expressions to Make Decisions in C#</a> module on Microsoft Learn. Then, answer the question below.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Which of the following lines of code is a valid use of the conditional operator?
|
||||
|
||||
## --answers--
|
||||
|
||||
`int value = amount >= 10 ? 10 : 20;`
|
||||
|
||||
---
|
||||
|
||||
`int value = amount >= 10 : 10 ? 20;`
|
||||
|
||||
---
|
||||
|
||||
`int value = amount >= 10 ? 10 | 20;`
|
||||
|
||||
## --video-solution--
|
||||
|
||||
1
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: 647e217e8acb466c97ccbee4
|
||||
title: Guided Project - Develop Conditional Branching and Looping Structures in C#
|
||||
challengeType: 19
|
||||
dashedName: guided-project-develop-conditional-branching-and-looping-structures-in-c-sharp
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
This challenge will be partially completed on the Microsoft Learn platform. Follow these instructions to complete the challenge:
|
||||
|
||||
1. Go to <a href="https://learn.microsoft.com/training/modules/guided-project-develop-conditional-branching-looping/" target="_blank" rel="noreferrer">https://learn.microsoft.com/training/modules/guided-project-develop-conditional-branching-looping/</a> and complete all the tasks for the "Guided Project - Develop Conditional Branching and Looping Structures in C#" module. This is **required** to earn the "Add Logic to C# Console Applications" trophy on Microsoft Learn, and qualify for the certification exam.
|
||||
1. When you are finished, come back and correctly answer the question below.
|
||||
|
||||
# --assignment--
|
||||
|
||||
Complete the <a href="https://learn.microsoft.com/training/modules/guided-project-develop-conditional-branching-looping/" target="_blank" rel="noreferrer">Develop Conditional Branching and Looping Structures in C#</a> guided project on Microsoft Learn. Then, answer the question below.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
When is it appropriate to use a `switch-case` construct rather than a `if-elseif-else` construct?
|
||||
|
||||
## --answers--
|
||||
|
||||
A `switch-case` construct is appropriate when more than 2-3 `else if` code blocks are required.
|
||||
|
||||
---
|
||||
|
||||
A `switch-case` construct is appropriate when the number of `case` patterns is small.
|
||||
|
||||
---
|
||||
|
||||
A `switch-case` construct is appropriate when the selection statement is inside a loop.
|
||||
|
||||
|
||||
## --video-solution--
|
||||
|
||||
1
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 647e218f8acb466c97ccbee5
|
||||
title: Iterate Through a Code Block Using the for Statement in C#
|
||||
challengeType: 19
|
||||
dashedName: iterate-through-a-code-block-using-the-for-statement-in-c-sharp
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
This challenge will be partially completed on the Microsoft Learn platform. Follow these instructions to complete the challenge:
|
||||
|
||||
1. Go to <a href="https://learn.microsoft.com/training/modules/csharp-for/" target="_blank" rel="noreferrer">https://learn.microsoft.com/training/modules/csharp-for/</a> and complete all the tasks for the "Iterate Through a Code Block Using the for Statement in C#" module. This is **required** to earn the "Add Logic to C# Console Applications" trophy on Microsoft Learn, and qualify for the certification exam.
|
||||
1. When you are finished, come back and correctly answer the question below.
|
||||
|
||||
# --assignment--
|
||||
|
||||
Complete the <a href="https://learn.microsoft.com/training/modules/csharp-for/" target="_blank" rel="noreferrer">Iterate Through a Code Block Using the `for` Statement in C#</a> module on Microsoft Learn. Then, answer the question below.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Which of the following `for` statements is correct?
|
||||
|
||||
## --answers--
|
||||
|
||||
`for (int x = 20: x < 80: x++)`
|
||||
|
||||
---
|
||||
|
||||
`for (int j = 0; j == 80; j + 1)`
|
||||
|
||||
---
|
||||
|
||||
`for (int counter = 20; counter < 80; counter++)`
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
---
|
||||
id: 647f882207d29547b3bee1c0
|
||||
title: Trophy - Add Logic to C# Console Applications
|
||||
challengeType: 18
|
||||
dashedName: trophy-add-logic-to-c-sharp-console-applications
|
||||
msTrophyId: learn.wwl.get-started-c-sharp-part-3.trophy
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now that you've completed all of the "Add Logic to C# Console Applications" challenges, you should have earned the trophy for it on the Microsoft Learn platform. If you don't see this trophy when viewing <a href="https://learn.microsoft.com/users/me/achievements#trophies-section" target="_blank" rel="noreferrer">your achievements page</a>, you have not completed all the tasks and your trophy cannot be verified.
|
||||
|
||||
Link your Microsoft username to your freeCodeCamp account and click the "Verify Trophy" button below to complete the challenge. This trophy is **required** to qualify for the certification exam.
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
---
|
||||
id: 5895f700f9fc0f352b528e63
|
||||
title: Set up a Template Engine
|
||||
challengeType: 2
|
||||
forumTopicId: 301564
|
||||
dashedName: set-up-a-template-engine
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Working on these challenges will involve you writing your code using one of the following methods:
|
||||
|
||||
- Clone <a href="https://github.com/freeCodeCamp/boilerplate-advancednode/" target="_blank" rel="noopener noreferrer nofollow">this GitHub repo</a> and complete these challenges locally.
|
||||
- Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo.
|
||||
|
||||
A template engine enables you to use static template files (such as those written in *Pug*) in your app. At runtime, the template engine replaces variables in a template file with actual values which can be supplied by your server. Then it transforms the template into a static HTML file that is sent to the client. This approach makes it easier to design an HTML page and allows for displaying variables on the page without needing to make an API call from the client.
|
||||
|
||||
`pug@~3.0.0` has already been installed, and is listed as a dependency in your `package.json` file.
|
||||
|
||||
Express needs to know which template engine you are using. Use the `set` method to assign `pug` as the `view engine` property's value:
|
||||
|
||||
```javascript
|
||||
app.set('view engine', 'pug');
|
||||
```
|
||||
|
||||
After that, add another `set` method that sets the `views` property of your `app` to point to the `./views/pug` directory. This tells Express to render all views relative to that directory.
|
||||
|
||||
Finally, use `res.render()` in the route for your home page, passing `index` as the first argument. This will render the `pug` template.
|
||||
|
||||
If all went as planned, your app home page will no longer be blank. Instead, it will display a message indicating you've successfully rendered the Pug template!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#set-up-a-template-engine-1" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Pug should be a dependency.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'pug',
|
||||
'Your project should list "pug" as a dependency'
|
||||
);
|
||||
```
|
||||
|
||||
View engine should be Pug.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/app", code);
|
||||
const res = await fetch(url);
|
||||
const app = await res.json();
|
||||
assert.equal(app?.settings?.['view engine'], "pug");
|
||||
```
|
||||
|
||||
You should set the `views` property of the application to `./views/pug`.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/app", code);
|
||||
const res = await fetch(url);
|
||||
const app = await res.json();
|
||||
assert.equal(app?.settings?.views, "./views/pug");
|
||||
```
|
||||
|
||||
Use the correct ExpressJS method to render the index page from the response.
|
||||
|
||||
```js
|
||||
const url = new URL("/", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/FCC Advanced Node and Express/gi,
|
||||
'You successfully rendered the Pug template!'
|
||||
);
|
||||
```
|
||||
|
||||
Pug should be working.
|
||||
|
||||
```js
|
||||
const url = new URL("/", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/pug-success-message/gi,
|
||||
'Your projects home page should now be rendered by pug with the projects .pug file unaltered'
|
||||
);
|
||||
```
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
---
|
||||
id: 5895f70bf9fc0f352b528e64
|
||||
title: Use a Template Engine's Powers
|
||||
challengeType: 2
|
||||
forumTopicId: 301567
|
||||
dashedName: use-a-template-engines-powers
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
One of the greatest features of using a template engine is being able to pass variables from the server to the template file before rendering it to HTML.
|
||||
|
||||
In your Pug file, you're able to use a variable by referencing the variable name as `#{variable_name}` inline with other text on an element or by using an equal sign on the element without a space such as `p=variable_name` which assigns the variable's value to the p element's text.
|
||||
|
||||
Pug is all about using whitespace and tabs to show nested elements and cutting down on the amount of code needed to make a beautiful site.
|
||||
|
||||
Take the following Pug code for example:
|
||||
|
||||
```pug
|
||||
head
|
||||
script(type='text/javascript').
|
||||
if (foo) bar(1 + 5);
|
||||
body
|
||||
if youAreUsingPug
|
||||
p You are amazing
|
||||
else
|
||||
p Get on it!
|
||||
```
|
||||
|
||||
The above yields the following HTML:
|
||||
|
||||
```html
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
if (foo) bar(1 + 5);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>You are amazing</p>
|
||||
</body>
|
||||
```
|
||||
|
||||
Your `index.pug` file included in your project, uses the variables `title` and `message`.
|
||||
|
||||
Pass those from your server to the Pug file by adding an object as a second argument to your `res.render` call with the variables and their values. Give the `title` a value of `Hello` and `message` a value of `Please log in`.
|
||||
|
||||
It should look like:
|
||||
|
||||
```javascript
|
||||
res.render('index', { title: 'Hello', message: 'Please log in' });
|
||||
```
|
||||
|
||||
Now refresh your page, and you should see those values rendered in your view in the correct spot as laid out in your `index.pug` file!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#use-a-template-engines-power-2" target="_blank" rel="noopener noreferrer nofollow">project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Pug should correctly render variables.
|
||||
|
||||
```js
|
||||
const url = new URL("/", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/pug-variable("|')>Please log in/gi,
|
||||
'Your projects home page should now be rendered by pug with the projects .pug file unaltered'
|
||||
);
|
||||
```
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
---
|
||||
id: 5895f70cf9fc0f352b528e65
|
||||
title: Set up Passport
|
||||
challengeType: 2
|
||||
forumTopicId: 301565
|
||||
dashedName: set-up-passport
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
It's time to set up *Passport* so you can finally start allowing a user to register or log in to an account. In addition to Passport, you will use Express-session to handle sessions. Express-session has a ton of advanced features you can use, but for now you are just going to use the basics. Using this middleware saves the session id as a cookie in the client, and allows us to access the session data using that id on the server. This way, you keep personal account information out of the cookie used by the client to tell to your server clients are authenticated and keep the *key* to access the data stored on the server.
|
||||
|
||||
`passport@~0.4.1` and `express-session@~1.17.1` are already installed, and are both listed as dependencies in your `package.json` file.
|
||||
|
||||
You will need to set up the session settings and initialize Passport. First, create the variables `session` and `passport` to require `express-session` and `passport` respectively.
|
||||
|
||||
Then, set up your Express app to use the session by defining the following options:
|
||||
|
||||
```javascript
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_SECRET,
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
cookie: { secure: false }
|
||||
}));
|
||||
```
|
||||
|
||||
Be sure to add `SESSION_SECRET` to your `.env` file, and give it a random value. This is used to compute the hash used to encrypt your cookie!
|
||||
|
||||
After you do all that, tell your express app to **use** `passport.initialize()` and `passport.session()`.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#set-up-passport-3" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Passport and Express-session should be dependencies.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport',
|
||||
'Your project should list "passport" as a dependency'
|
||||
);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'express-session',
|
||||
'Your project should list "express-session" as a dependency'
|
||||
);
|
||||
```
|
||||
|
||||
Dependencies should be correctly required.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport("|')/,
|
||||
'You should have required passport'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')express-session("|')/,
|
||||
'You should have required express-session'
|
||||
);
|
||||
```
|
||||
|
||||
Express app should use new dependencies.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(data, /passport\.initialize/, 'Your express app should use "passport.initialize()"');
|
||||
assert.match(data, /passport\.session/, 'Your express app should use "passport.session()"');
|
||||
```
|
||||
|
||||
Session and session secret should be correctly set up.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/secret *:\s*process\.env(\.SESSION_SECRET|\[(?<q>"|')SESSION_SECRET\k<q>\])/,
|
||||
'Your express app should have express-session set up with your secret as process.env.SESSION_SECRET'
|
||||
);
|
||||
```
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
---
|
||||
id: 5895f70cf9fc0f352b528e66
|
||||
title: Serialization of a User Object
|
||||
challengeType: 2
|
||||
forumTopicId: 301563
|
||||
dashedName: serialization-of-a-user-object
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Serialization and deserialization are important concepts in regard to authentication. To serialize an object means to convert its contents into a small *key* that can then be deserialized into the original object. This is what allows us to know who has communicated with the server without having to send the authentication data, like the username and password, at each request for a new page.
|
||||
|
||||
To set this up properly, you need to have a serialize function and a deserialize function. In Passport, these can be created with:
|
||||
|
||||
```javascript
|
||||
passport.serializeUser(cb);
|
||||
passport.deserializeUser(cb);
|
||||
```
|
||||
|
||||
The callback function passed to `serializeUser` is called with two arguments: the full user object, and a callback used by passport.
|
||||
|
||||
The callback expects two arguments: An error, if any, and a unique key to identify the user that should be returned in the callback. You will use the user's `_id` in the object. This is guaranteed to be unique, as it is generated by MongoDB.
|
||||
|
||||
Similarly, `deserializeUser` is called with two arguments: the unique key, and a callback function.
|
||||
|
||||
This callback expects two arguments: An error, if any, and the full user object. To get the full user object, make a query search for a Mongo `_id`, as shown below:
|
||||
|
||||
|
||||
```javascript
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user._id);
|
||||
});
|
||||
|
||||
passport.deserializeUser((id, done) => {
|
||||
myDataBase.findOne({ _id: new ObjectID(id) }, (err, doc) => {
|
||||
done(null, null);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Add the two functions above to your server. The `ObjectID` class comes from the `mongodb` package. `mongodb@~3.6.0` has already been added as a dependency. Declare this class with:
|
||||
|
||||
```javascript
|
||||
const { ObjectID } = require('mongodb');
|
||||
```
|
||||
|
||||
The `deserializeUser` will throw an error until you set up the database connection. So, for now, comment out the `myDatabase.findOne` call, and just call `done(null, null)` in the `deserializeUser` callback function.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#serialization-of-a-user-object-4" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should serialize the user object correctly.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/passport.serializeUser/gi,
|
||||
'You should have created your passport.serializeUser function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*user._id/gi,
|
||||
'There should be a callback in your serializeUser with (null, user._id)'
|
||||
);
|
||||
```
|
||||
|
||||
You should deserialize the user object correctly.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/passport.deserializeUser/gi,
|
||||
'You should have created your passport.deserializeUser function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*null/gi,
|
||||
'There should be a callback in your deserializeUser with (null, null) for now'
|
||||
);
|
||||
```
|
||||
|
||||
MongoDB should be a dependency.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'mongodb',
|
||||
'Your project should list "mongodb" as a dependency'
|
||||
);
|
||||
```
|
||||
|
||||
Mongodb should be properly required including the ObjectId.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')mongodb\1/gi,
|
||||
'You should have required mongodb'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/new ObjectID.*id/gi,
|
||||
'Even though the block is commented out, you should use new ObjectID(id) for when we add the database'
|
||||
);
|
||||
```
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
---
|
||||
id: 5895f70cf9fc0f352b528e67
|
||||
title: Implement the Serialization of a Passport User
|
||||
challengeType: 2
|
||||
forumTopicId: 301556
|
||||
dashedName: implement-the-serialization-of-a-passport-user
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You are not loading an actual user object since the database is not set up. Connect to the database once, when you start the server, and keep a persistent connection for the full life-cycle of the app. To do this, add your database's connection string (for example: `mongodb+srv://<username>:<password>@cluster0-jvwxi.mongodb.net/?retryWrites=true&w=majority`) to the environment variable `MONGO_URI`. This is used in the `connection.js` file.
|
||||
|
||||
*If you are having issues setting up a free database on MongoDB Atlas, check out this <a href="https://www.freecodecamp.org/news/get-started-with-mongodb-atlas/" target="_blank" rel="noopener noreferrer nofollow">tutorial</a>.*
|
||||
|
||||
Now you want to connect to your database, then start listening for requests. The purpose of this is to not allow requests before your database is connected or if there is a database error. To accomplish this, encompass your serialization and app routes in the following code:
|
||||
|
||||
```javascript
|
||||
myDB(async client => {
|
||||
const myDataBase = await client.db('database').collection('users');
|
||||
|
||||
// Be sure to change the title
|
||||
app.route('/').get((req, res) => {
|
||||
// Change the response to render the Pug template
|
||||
res.render('index', {
|
||||
title: 'Connected to Database',
|
||||
message: 'Please login'
|
||||
});
|
||||
});
|
||||
|
||||
// Serialization and deserialization here...
|
||||
|
||||
// Be sure to add this...
|
||||
}).catch(e => {
|
||||
app.route('/').get((req, res) => {
|
||||
res.render('index', { title: e, message: 'Unable to connect to database' });
|
||||
});
|
||||
});
|
||||
// app.listen out here...
|
||||
```
|
||||
|
||||
Be sure to uncomment the `myDataBase` code in `deserializeUser`, and edit your `done(null, null)` to include the `doc`.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#implement-the-serialization-of-a-passport-user-5" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Database connection should be present.
|
||||
|
||||
```js
|
||||
const url = new URL("/", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/Connected to Database/gi,
|
||||
'You successfully connected to the database!'
|
||||
);
|
||||
```
|
||||
|
||||
Deserialization should now be correctly using the DB and `done(null, null)` should be called with the `doc`.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*doc/gi,
|
||||
'The callback in deserializeUser of (null, null) should be altered to (null, doc)'
|
||||
);
|
||||
```
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
---
|
||||
id: 5895f70df9fc0f352b528e68
|
||||
title: Authentication Strategies
|
||||
challengeType: 2
|
||||
forumTopicId: 301547
|
||||
dashedName: authentication-strategies
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
A strategy is a way of authenticating a user. You can use a strategy for allowing users to authenticate based on locally saved information (if you have them register first) or from a variety of providers such as Google or GitHub. For this project, we will use Passport middleware. Passport provides a comprehensive set of strategies that support authentication using a username and password, GitHub, Google, and more.
|
||||
|
||||
`passport-local@~1.0.0` has already been added as a dependency. Add it to your server as follows:
|
||||
|
||||
```javascript
|
||||
const LocalStrategy = require('passport-local');
|
||||
```
|
||||
|
||||
Tell passport to **use** an instantiated `LocalStrategy` object with a few settings defined. Make sure this (as well as everything from this point on) is encapsulated in the database connection since it relies on it!:
|
||||
|
||||
```javascript
|
||||
passport.use(new LocalStrategy((username, password, done) => {
|
||||
myDataBase.findOne({ username: username }, (err, user) => {
|
||||
console.log(`User ${username} attempted to log in.`);
|
||||
if (err) return done(err);
|
||||
if (!user) return done(null, false);
|
||||
if (password !== user.password) return done(null, false);
|
||||
return done(null, user);
|
||||
});
|
||||
}));
|
||||
```
|
||||
|
||||
This is defining the process to use when you try to authenticate someone locally. First, it tries to find a user in your database with the username entered. Then, it checks for the password to match. Finally, if no errors have popped up that you checked for (e.g. an incorrect password), the `user` object is returned and they are authenticated.
|
||||
|
||||
Many strategies are set up using different settings. Generally, it is easy to set it up based on the README in that strategy's repository. A good example of this is the GitHub strategy where you don't need to worry about a username or password because the user will be sent to GitHub's auth page to authenticate. As long as they are logged in and agree then GitHub returns their profile for you to use.
|
||||
|
||||
In the next step, you will set up how to actually call the authentication strategy to validate a user based on form data.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#authentication-strategies-6" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Passport-local should be a dependency.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport-local',
|
||||
'Your project should list "passport-local " as a dependency'
|
||||
);
|
||||
```
|
||||
|
||||
Passport-local should be correctly required and set up.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport-local("|')/,
|
||||
'You should have required passport-local'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/new LocalStrategy/,
|
||||
'You should have told passport to use a new strategy'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/findOne/,
|
||||
'Your new local strategy should use the findOne query to find a username based on the inputs'
|
||||
);
|
||||
```
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
---
|
||||
id: 5895f70df9fc0f352b528e69
|
||||
title: How to Use Passport Strategies
|
||||
challengeType: 2
|
||||
forumTopicId: 301555
|
||||
dashedName: how-to-use-passport-strategies
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In the `index.pug` file supplied, there is a login form. It is hidden because of the inline JavaScript `if showLogin` with the form indented after it.
|
||||
|
||||
In the `res.render` for that page, add a new variable to the object, `showLogin: true`. When you refresh your page, you should then see the form! This form is set up to **POST** on `/login`. So, this is where you should set up to accept the POST request and authenticate the user.
|
||||
|
||||
For this challenge, you should add the route `/login` to accept a POST request. To authenticate on this route, you need to add a middleware to do so before then sending a response. This is done by just passing another argument with the middleware before with your response. The middleware to use is `passport.authenticate('local')`.
|
||||
|
||||
`passport.authenticate` can also take some options as an argument such as `{ failureRedirect: '/' }` which is incredibly useful, so be sure to add that in as well. Add a response after using the middleware (which will only be called if the authentication middleware passes) that redirects the user to `/profile`. Add that route, as well, and make it render the view `profile.pug`.
|
||||
|
||||
If the authentication was successful, the user object will be saved in `req.user`.
|
||||
|
||||
At this point, if you enter a username and password in the form, it should redirect to the home page `/`, and the console of your server should display `'User {USERNAME} attempted to log in.'`, since we currently cannot login a user who isn't registered.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#how-to-use-passport-strategies-7" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
All steps should be correctly implemented in `server.js`.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/showLogin:( |)true/,
|
||||
'You should be passing the variable "showLogin" as true to your render function for the homepage'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/failureRedirect:( |)('|")\/('|")/,
|
||||
'Your code should include a failureRedirect to the "/" route'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/login[^]*post[^]*local/,
|
||||
'You should have a route for login which accepts a POST and passport.authenticates local'
|
||||
);
|
||||
```
|
||||
|
||||
A POST request to `/login` should correctly redirect to `/`.
|
||||
|
||||
```js
|
||||
const url = new URL("/login", code);
|
||||
const res = await fetch(url, { method: 'POST' });
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/Looks like this page is being rendered from Pug into HTML!/,
|
||||
'A login attempt at this point should redirect to the homepage since we do not have any registered users'
|
||||
);
|
||||
```
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
---
|
||||
id: 5895f70df9fc0f352b528e6a
|
||||
title: Create New Middleware
|
||||
challengeType: 2
|
||||
forumTopicId: 301551
|
||||
dashedName: create-new-middleware
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
As is, any user can just go to `/profile` whether they have authenticated or not by typing in the URL. You want to prevent this by checking if the user is authenticated first before rendering the profile page. This is the perfect example of when to create a middleware.
|
||||
|
||||
The challenge here is creating the middleware function `ensureAuthenticated(req, res, next)`, which will check if a user is authenticated by calling Passport's `isAuthenticated` method on the `request` which checks if `req.user` is defined. If it is, then `next()` should be called. Otherwise, you can just respond to the request with a redirect to your homepage to login.
|
||||
|
||||
An implementation of this middleware is:
|
||||
|
||||
```javascript
|
||||
function ensureAuthenticated(req, res, next) {
|
||||
if (req.isAuthenticated()) {
|
||||
return next();
|
||||
}
|
||||
res.redirect('/');
|
||||
};
|
||||
```
|
||||
|
||||
Create the above middleware function, then pass `ensureAuthenticated` as middleware to requests for the profile page before the argument to the GET request:
|
||||
|
||||
```javascript
|
||||
app
|
||||
.route('/profile')
|
||||
.get(ensureAuthenticated, (req,res) => {
|
||||
res.render('profile');
|
||||
});
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#create-new-middleware-8" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
The middleware `ensureAuthenticated` should be implemented and attached to the `/profile` route.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/ensureAuthenticated[^]*req.isAuthenticated/,
|
||||
'Your ensureAuthenticated middleware should be defined and utilize the req.isAuthenticated function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/profile[^]*get[^]*ensureAuthenticated/,
|
||||
'Your ensureAuthenticated middleware should be attached to the /profile route'
|
||||
);
|
||||
```
|
||||
|
||||
An unauthenticated GET request to `/profile` should correctly redirect to `/`.
|
||||
|
||||
```js
|
||||
const url = new URL("/profile", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/Home page/,
|
||||
'An attempt to go to the profile at this point should redirect to the homepage since we are not logged in'
|
||||
);
|
||||
```
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
---
|
||||
id: 5895f70ef9fc0f352b528e6b
|
||||
title: How to Put a Profile Together
|
||||
challengeType: 2
|
||||
forumTopicId: 301554
|
||||
dashedName: how-to-put-a-profile-together
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now that you can ensure the user accessing the `/profile` is authenticated, you can use the information contained in `req.user` on your page.
|
||||
|
||||
Pass an object containing the property `username` and value of `req.user.username` as the second argument for the `render` method of the profile view.
|
||||
|
||||
Then, go to your `profile.pug` view, and add the following line below the existing `h1` element, and at the same level of indentation:
|
||||
|
||||
```pug
|
||||
h2.center#welcome Welcome, #{username}!
|
||||
```
|
||||
|
||||
This creates an `h2` element with the class `center` and id `welcome` containing the text `Welcome, ` followed by the username.
|
||||
|
||||
Also, in `profile.pug`, add a link referring to the `/logout` route, which will host the logic to unauthenticate a user:
|
||||
|
||||
```pug
|
||||
a(href='/logout') Logout
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#how-to-put-a-profile-together-9" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should correctly add a Pug render variable to `/profile`.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/username:( |)req.user.username/,
|
||||
'You should be passing the variable username with req.user.username into the render function of the profile page'
|
||||
);
|
||||
```
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
---
|
||||
id: 58965611f9fc0f352b528e6c
|
||||
title: Logging a User Out
|
||||
challengeType: 2
|
||||
forumTopicId: 301560
|
||||
dashedName: logging-a-user-out
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Creating the logout logic is easy. The route should just unauthenticate the user, and redirect to the home page instead of rendering any view.
|
||||
|
||||
In passport, unauthenticating a user is as easy as just calling `req.logout()` before redirecting. Add this `/logout` route to do that:
|
||||
|
||||
```js
|
||||
app.route('/logout')
|
||||
.get((req, res) => {
|
||||
req.logout();
|
||||
res.redirect('/');
|
||||
});
|
||||
```
|
||||
|
||||
You may have noticed that you are not handling missing pages (404). The common way to handle this in Node is with the following middleware. Go ahead and add this in after all your other routes:
|
||||
|
||||
```js
|
||||
app.use((req, res, next) => {
|
||||
res.status(404)
|
||||
.type('text')
|
||||
.send('Not Found');
|
||||
});
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#logging-a-user-out-10" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
`req.logout()` should be called in your `/logout` route.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/req.logout/gi,
|
||||
'You should be calling req.logout() in your /logout route'
|
||||
);
|
||||
```
|
||||
|
||||
`/logout` should redirect to the home page.
|
||||
|
||||
```js
|
||||
const url = new URL("/logout", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/Home page/gi,
|
||||
'When a user logs out they should be redirected to the homepage'
|
||||
);
|
||||
```
|
||||
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
---
|
||||
id: 58966a17f9fc0f352b528e6d
|
||||
title: Registration of New Users
|
||||
challengeType: 2
|
||||
forumTopicId: 301561
|
||||
dashedName: registration-of-new-users
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now you need to allow a new user on your site to register an account. In the `res.render` for the home page add a new variable to the object passed along - `showRegistration: true`. When you refresh your page, you should then see the registration form that was already created in your `index.pug` file. This form is set up to **POST** on `/register`, so create that route and have it add the user object to the database by following the logic below.
|
||||
|
||||
The logic of the registration route should be as follows:
|
||||
|
||||
1. Register the new user
|
||||
2. Authenticate the new user
|
||||
3. Redirect to `/profile`
|
||||
|
||||
The logic of step 1 should be as follows:
|
||||
|
||||
1. Query database with `findOne`
|
||||
2. If there is an error, call `next` with the error
|
||||
3. If a user is returned, redirect back to home
|
||||
4. If a user is not found and no errors occur, then `insertOne` into the database with the username and password. As long as no errors occur there, call `next` to go to step 2, authenticating the new user, which you already wrote the logic for in your `POST /login` route.
|
||||
|
||||
```js
|
||||
app.route('/register')
|
||||
.post((req, res, next) => {
|
||||
myDataBase.findOne({ username: req.body.username }, (err, user) => {
|
||||
if (err) {
|
||||
next(err);
|
||||
} else if (user) {
|
||||
res.redirect('/');
|
||||
} else {
|
||||
myDataBase.insertOne({
|
||||
username: req.body.username,
|
||||
password: req.body.password
|
||||
},
|
||||
(err, doc) => {
|
||||
if (err) {
|
||||
res.redirect('/');
|
||||
} else {
|
||||
// The inserted document is held within
|
||||
// the ops property of the doc
|
||||
next(null, doc.ops[0]);
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
passport.authenticate('local', { failureRedirect: '/' }),
|
||||
(req, res, next) => {
|
||||
res.redirect('/profile');
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#registration-of-new-users-11" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
**NOTE:** From this point onwards, issues can arise relating to the use of the *picture-in-picture* browser. If you are using an online IDE which offers a preview of the app within the editor, it is recommended to open this preview in a new tab.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should have a `/register` route and display a registration form on the home page.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/showRegistration:( |)true/gi,
|
||||
'You should be passing the variable showRegistration as true to your render function for the homepage'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/register[^]*post[^]*findOne[^]*username:( |)req.body.username/gi,
|
||||
'You should have a route that accepts a POST request on /register that queries the db with findOne and the query being username: req.body.username'
|
||||
);
|
||||
```
|
||||
|
||||
Registering should work.
|
||||
|
||||
```js
|
||||
const url = code;
|
||||
const user = `freeCodeCampTester${Date.now()}`;
|
||||
const xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
test(this);
|
||||
} else {
|
||||
throw new Error(`${this.status} ${this.statusText}`);
|
||||
}
|
||||
};
|
||||
xhttp.open('POST', url + '/register', true);
|
||||
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
xhttp.send(`username=${user}&password=${user}`);
|
||||
function test(xhttpRes) {
|
||||
const data = xhttpRes.responseText;
|
||||
assert.match(
|
||||
data,
|
||||
/Profile/gi,
|
||||
'Register should work, and redirect successfully to the profile.'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Login should work.
|
||||
|
||||
```js
|
||||
const url = code;
|
||||
const user = `freeCodeCampTester${Date.now()}`;
|
||||
const xhttpReg = new XMLHttpRequest();
|
||||
xhttpReg.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
login();
|
||||
} else {
|
||||
throw new Error(`${this.status} ${this.statusText}`);
|
||||
}
|
||||
};
|
||||
xhttpReg.open('POST', url + '/register', true);
|
||||
xhttpReg.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
xhttpReg.send(`username=${user}&password=${user}`);
|
||||
function login() {
|
||||
const xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
test(this);
|
||||
} else {
|
||||
throw new Error(`${this.status} ${this.statusText}`);
|
||||
}
|
||||
};
|
||||
xhttp.open('POST', url + '/login', true);
|
||||
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
xhttp.send(`username=${user}&password=${user}`);
|
||||
}
|
||||
function test(xhttpRes) {
|
||||
const data = xhttpRes.responseText;
|
||||
assert.match(
|
||||
data,
|
||||
/Profile/gi,
|
||||
'Login should work if previous test was done successfully and redirect successfully to the profile.'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
new RegExp(user, 'g'),
|
||||
'The profile should properly display the welcome to the user logged in'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Logout should work.
|
||||
|
||||
```js
|
||||
$.ajax({
|
||||
url: code + '/logout',
|
||||
type: 'GET',
|
||||
xhrFields: { withCredentials: true }
|
||||
}).then(
|
||||
(data) => {
|
||||
assert.match(data, /Home/gi, 'Logout should redirect to home');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Profile should no longer work after logout.
|
||||
|
||||
```js
|
||||
$.ajax({
|
||||
url: code + '/profile',
|
||||
type: 'GET',
|
||||
crossDomain: true,
|
||||
xhrFields: { withCredentials: true }
|
||||
}).then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/Home/gi,
|
||||
'Profile should redirect to home when we are logged out now again'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
---
|
||||
id: 589690e6f9fc0f352b528e6e
|
||||
title: Clean Up Your Project with Modules
|
||||
challengeType: 2
|
||||
forumTopicId: 301549
|
||||
dashedName: clean-up-your-project-with-modules
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Right now, everything you have is in your `server.js` file. This can lead to hard to manage code that isn't very expandable. Create 2 new files: `routes.js` and `auth.js`
|
||||
|
||||
Both should start with the following code:
|
||||
|
||||
```js
|
||||
module.exports = function (app, myDataBase) {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now, in the top of your server file, require these files like so: `const routes = require('./routes.js');` Right after you establish a successful connection with the database, instantiate each of them like so: `routes(app, myDataBase)`
|
||||
|
||||
Finally, take all of the routes in your server and paste them into your new files, and remove them from your server file. Also take the `ensureAuthenticated` function, since it was specifically created for routing. Now, you will have to correctly add the dependencies in which are used, such as `const passport = require('passport');`, at the very top, above the export line in your `routes.js` file.
|
||||
|
||||
Keep adding them until no more errors exist, and your server file no longer has any routing (**except for the route in the catch block**)!
|
||||
|
||||
Do the same thing in your `auth.js` file with all of the things related to authentication such as the serialization and the setting up of the local strategy and erase them from your server file. Be sure to add the dependencies in and call `auth(app, myDataBase)` in the server in the same spot.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#clean-up-your-project-with-modules-2" target="_blank" rel="noopener noreferrer nofollow">check out an example of the completed project</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Modules should be present.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require\s*\(('|")\.\/routes(\.js)?\1\)/gi,
|
||||
'You should have required your new files'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/client\s*\.db[^]*routes/gi,
|
||||
'Your new modules should be called after your connection to the database'
|
||||
);
|
||||
```
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
---
|
||||
id: 589a69f5f9fc0f352b528e70
|
||||
title: Implementation of Social Authentication
|
||||
challengeType: 2
|
||||
forumTopicId: 301559
|
||||
dashedName: implementation-of-social-authentication
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The basic path this kind of authentication will follow in your app is:
|
||||
|
||||
1. User clicks a button or link sending them to your route to authenticate using a specific strategy (e.g. GitHub).
|
||||
2. Your route calls `passport.authenticate('github')` which redirects them to GitHub.
|
||||
3. The page the user lands on, on GitHub, allows them to login if they aren't already. It then asks them to approve access to their profile from your app.
|
||||
4. The user is then returned to your app at a specific callback url with their profile if they are approved.
|
||||
5. They are now authenticated, and your app should check if it is a returning profile, or save it in your database if it is not.
|
||||
|
||||
Strategies with OAuth require you to have at least a *Client ID* and a *Client Secret* which is a way for the service to verify who the authentication request is coming from and if it is valid. These are obtained from the site you are trying to implement authentication with, such as GitHub, and are unique to your app--**THEY ARE NOT TO BE SHARED** and should never be uploaded to a public repository or written directly in your code. A common practice is to put them in your `.env` file and reference them like so: `process.env.GITHUB_CLIENT_ID`. For this challenge you are going to use the GitHub strategy.
|
||||
|
||||
Follow <a href="https://www.freecodecamp.org/news/how-to-set-up-a-github-oauth-application/" target="_blank" rel="noopener noreferrer nofollow">these instructions</a> to obtain your *Client ID and Secret* from GitHub. Set the homepage URL to your homepage (**not the project code's URL**), and set the callback URL to the same homepage URL with `/auth/github/callback` appended to the end. Save the client ID and your client secret in your project's `.env` file as `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`.
|
||||
|
||||
In your `routes.js` file, add `showSocialAuth: true` to the homepage route, after `showRegistration: true`. Now, create 2 routes accepting GET requests: `/auth/github` and `/auth/github/callback`. The first should only call passport to authenticate `'github'`. The second should call passport to authenticate `'github'` with a failure redirect to `/`, and then if that is successful redirect to `/profile` (similar to your last project).
|
||||
|
||||
An example of how `/auth/github/callback` should look is similar to how you handled a normal login:
|
||||
|
||||
```js
|
||||
app.route('/login')
|
||||
.post(passport.authenticate('local', { failureRedirect: '/' }), (req,res) => {
|
||||
res.redirect('/profile');
|
||||
});
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#implementation-of-social-authentication-3" target="_blank" rel="noopener noreferrer nofollow">check out the project up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Route `/auth/github` should be correct.
|
||||
|
||||
```js
|
||||
try {
|
||||
const res = await fetch(code + '/_api/routes.js');
|
||||
if (res.ok) {
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data.replace(/\s/g, ''),
|
||||
/passport.authenticate.*?github/g,
|
||||
'Route auth/github should only call passport.authenticate with github'
|
||||
);
|
||||
} else {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
const res2 = await fetch(code + '/_api/app-stack');
|
||||
if (res2.ok) {
|
||||
const data2 = JSON.parse(await res2.json());
|
||||
const dataLayer = data2.find(layer => layer?.route?.path === '/auth/github');
|
||||
assert.deepInclude(dataLayer?.route, { methods: {get: true}, path: "/auth/github"});
|
||||
assert.deepInclude(dataLayer?.route?.stack?.[0], {method: "get", name: "authenticate"});
|
||||
} else {
|
||||
throw new Error(res2.statusText);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
```
|
||||
|
||||
Route `/auth/github/callback` should be correct.
|
||||
|
||||
```js
|
||||
try {
|
||||
const res = await fetch(code + '/_api/routes.js');
|
||||
if (res.ok) {
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data.replace(/\s/g, ''),
|
||||
/failureRedirect:("|')\/\1/g,
|
||||
'Route auth/github/callback should accept a get request and call passport.authenticate for github with a failure redirect to home'
|
||||
);
|
||||
} else {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
const res2 = await fetch(code + '/_api/app-stack');
|
||||
if (res2.ok) {
|
||||
const data2 = JSON.parse(await res2.json());
|
||||
const dataLayer = data2.find(layer => layer?.route?.path === '/auth/github/callback');
|
||||
assert.deepInclude(dataLayer?.route, { methods: {get: true}, path: "/auth/github/callback"});
|
||||
assert.deepInclude(dataLayer?.route?.stack?.[0], {method: "get", name: "authenticate"});
|
||||
} else {
|
||||
throw new Error(res2.statusText);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
```
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
---
|
||||
id: 589a69f5f9fc0f352b528e71
|
||||
title: Implementation of Social Authentication II
|
||||
challengeType: 2
|
||||
forumTopicId: 301557
|
||||
dashedName: implementation-of-social-authentication-ii
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The last part of setting up your GitHub authentication is to create the strategy itself. `passport-github@~1.1.0` has already been added as a dependency, so require it in your `auth.js` file as `GitHubStrategy` like this: `const GitHubStrategy = require('passport-github').Strategy;`. Do not forget to require and configure `dotenv` to use your environment variables.
|
||||
|
||||
To set up the GitHub strategy, you have to tell Passport to use an instantiated `GitHubStrategy`, which accepts 2 arguments: an object (containing `clientID`, `clientSecret`, and `callbackURL`) and a function to be called when a user is successfully authenticated, which will determine if the user is new and what fields to save initially in the user's database object. This is common across many strategies, but some may require more information as outlined in that specific strategy's GitHub README. For example, Google requires a *scope* as well which determines what kind of information your request is asking to be returned and asks the user to approve such access.
|
||||
|
||||
The current strategy you are implementing authenticates users using a GitHub account and OAuth 2.0 tokens. The client ID and secret obtained when creating an application are supplied as options when creating the strategy. The strategy also requires a `verify` callback, which receives the access token and optional refresh token, as well as `profile` which contains the authenticated user's GitHub profile. The `verify` callback must call `cb` providing a user to complete authentication.
|
||||
|
||||
Here's how your new strategy should look at this point:
|
||||
|
||||
```js
|
||||
passport.use(new GitHubStrategy({
|
||||
clientID: process.env.GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
||||
callbackURL: /*INSERT CALLBACK URL ENTERED INTO GITHUB HERE*/
|
||||
},
|
||||
function(accessToken, refreshToken, profile, cb) {
|
||||
console.log(profile);
|
||||
//Database logic here with callback containing your user object
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
Your authentication won't be successful yet, and it will actually throw an error without the database logic and callback, but it should log your GitHub profile to your console if you try it!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#implementation-of-social-authentication-ii-4" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
`passport-github` dependency should be added.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport-github',
|
||||
'Your project should list "passport-github" as a dependency'
|
||||
);
|
||||
```
|
||||
|
||||
`passport-github` should be required.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/auth.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport-github("|')/gi,
|
||||
'You should have required passport-github'
|
||||
);
|
||||
```
|
||||
|
||||
GitHub strategy should be setup correctly thus far.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/auth.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/passport\.use.*new GitHubStrategy/gis,
|
||||
'Passport should use a new GitHubStrategy'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/callbackURL:\s*("|').*("|')/gi,
|
||||
'You should have a callbackURL'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/process\.env(\.GITHUB_CLIENT_SECRET|\[(?<q>"|')GITHUB_CLIENT_SECRET\k<q>\])/g,
|
||||
'You should use process.env.GITHUB_CLIENT_SECRET'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/process\.env(\.GITHUB_CLIENT_ID|\[(?<q>"|')GITHUB_CLIENT_ID\k<q>\])/g,
|
||||
'You should use process.env.GITHUB_CLIENT_ID'
|
||||
);
|
||||
```
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
---
|
||||
id: 589a8eb3f9fc0f352b528e72
|
||||
title: Implementation of Social Authentication III
|
||||
challengeType: 2
|
||||
forumTopicId: 301558
|
||||
dashedName: implementation-of-social-authentication-iii
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The final part of the strategy is handling the profile returned from GitHub. We need to load the user's database object if it exists, or create one if it doesn't, and populate the fields from the profile, then return the user's object. GitHub supplies us a unique *id* within each profile which we can use to search with to serialize the user with (already implemented). Below is an example implementation you can use in your project--it goes within the function that is the second argument for the new strategy, right below where `console.log(profile);` currently is:
|
||||
|
||||
```js
|
||||
myDataBase.findOneAndUpdate(
|
||||
{ id: profile.id },
|
||||
{
|
||||
$setOnInsert: {
|
||||
id: profile.id,
|
||||
username: profile.username,
|
||||
name: profile.displayName || 'John Doe',
|
||||
photo: profile.photos[0].value || '',
|
||||
email: Array.isArray(profile.emails)
|
||||
? profile.emails[0].value
|
||||
: 'No public email',
|
||||
created_on: new Date(),
|
||||
provider: profile.provider || ''
|
||||
},
|
||||
$set: {
|
||||
last_login: new Date()
|
||||
},
|
||||
$inc: {
|
||||
login_count: 1
|
||||
}
|
||||
},
|
||||
{ upsert: true, new: true },
|
||||
(err, doc) => {
|
||||
return cb(null, doc.value);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
`findOneAndUpdate` allows you to search for an object and update it. If the object doesn't exist, it will be inserted and made available to the callback function. In this example, we always set `last_login`, increment the `login_count` by `1`, and only populate the majority of the fields when a new object (new user) is inserted. Notice the use of default values. Sometimes a profile returned won't have all the information filled out or the user will keep it private. In this case, you handle it to prevent an error.
|
||||
|
||||
You should be able to login to your app now. Try it!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#implementation-of-social-authentication-iii-5" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
GitHub strategy setup should be complete.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/auth.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/GitHubStrategy[^]*myDataBase/gi,
|
||||
'Strategy should use now use the database to search for the user'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/GitHubStrategy[^]*cb/gi,
|
||||
'Strategy should return the callback function "cb"'
|
||||
);
|
||||
```
|
||||
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
---
|
||||
id: 589fc830f9fc0f352b528e74
|
||||
title: Set up the Environment
|
||||
challengeType: 2
|
||||
forumTopicId: 301566
|
||||
dashedName: set-up-the-environment
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The following challenges will make use of the `chat.pug` file. So, in your `routes.js` file, add a GET route pointing to `/chat` which makes use of `ensureAuthenticated`, and renders `chat.pug`, with `{ user: req.user }` passed as an argument to the response. Now, alter your existing `/auth/github/callback` route to set the `req.session.user_id = req.user.id`, and redirect to `/chat`.
|
||||
|
||||
`socket.io@~2.3.0` has already been added as a dependency, so require/instantiate it in your server as follows with `http` (comes built-in with Node.js):
|
||||
|
||||
```javascript
|
||||
const http = require('http').createServer(app);
|
||||
const io = require('socket.io')(http);
|
||||
```
|
||||
|
||||
Now that the *http* server is mounted on the *express app*, you need to listen from the *http* server. Change the line with `app.listen` to `http.listen`.
|
||||
|
||||
The first thing needing to be handled is listening for a new connection from the client. The <dfn>on</dfn> keyword does just that- listen for a specific event. It requires 2 arguments: a string containing the title of the event that's emitted, and a function with which the data is passed through. In the case of our connection listener, use `socket` to define the data in the second argument. A socket is an individual client who is connected.
|
||||
|
||||
To listen for connections to your server, add the following within your database connection:
|
||||
|
||||
```javascript
|
||||
io.on('connection', socket => {
|
||||
console.log('A user has connected');
|
||||
});
|
||||
```
|
||||
|
||||
Now for the client to connect, you just need to add the following to your `client.js` which is loaded by the page after you've authenticated:
|
||||
|
||||
```js
|
||||
/*global io*/
|
||||
let socket = io();
|
||||
```
|
||||
|
||||
The comment suppresses the error you would normally see since 'io' is not defined in the file. You have already added a reliable CDN to the Socket.IO library on the page in `chat.pug`.
|
||||
|
||||
Now try loading up your app and authenticate and you should see in your server console `A user has connected`.
|
||||
|
||||
**Note:**`io()` works only when connecting to a socket hosted on the same url/server. For connecting to an external socket hosted elsewhere, you would use `io.connect('URL');`.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#set-up-the-environment-6" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
`socket.io` should be a dependency.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'socket.io',
|
||||
'Your project should list "socket.io" as a dependency'
|
||||
);
|
||||
```
|
||||
|
||||
You should correctly require and instantiate `http` as `http`.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/http.*=.*require.*('|")http\1/s,
|
||||
'Your project should list "http" as a dependency'
|
||||
);
|
||||
```
|
||||
|
||||
You should correctly require and instantiate `socket.io` as `io`.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/io.*=.*require.*('|")socket.io\1.*http/s,
|
||||
'You should correctly require and instantiate socket.io as io.'
|
||||
);
|
||||
```
|
||||
|
||||
Socket.IO should be listening for connections.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/io.on.*('|")connection\1.*socket/s,
|
||||
'io should listen for "connection" and socket should be the 2nd arguments variable'
|
||||
);
|
||||
```
|
||||
|
||||
Your client should connect to your server.
|
||||
|
||||
```js
|
||||
const url = new URL("/public/client.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.*=.*io/s,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
```
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
---
|
||||
id: 589fc831f9fc0f352b528e75
|
||||
title: Communicate by Emitting
|
||||
challengeType: 2
|
||||
forumTopicId: 301550
|
||||
dashedName: communicate-by-emitting
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
<dfn>Emit</dfn> is the most common way of communicating you will use. When you emit something from the server to 'io', you send an event's name and data to all the connected sockets. A good example of this concept would be emitting the current count of connected users each time a new user connects!
|
||||
|
||||
Start by adding a variable to keep track of the users, just before where you are currently listening for connections.
|
||||
|
||||
```js
|
||||
let currentUsers = 0;
|
||||
```
|
||||
|
||||
Now, when someone connects, you should increment the count before emitting the count. So, you will want to add the incrementer within the connection listener.
|
||||
|
||||
```js
|
||||
++currentUsers;
|
||||
```
|
||||
|
||||
Finally, after incrementing the count, you should emit the event (still within the connection listener). The event should be named 'user count', and the data should just be the `currentUsers`.
|
||||
|
||||
```js
|
||||
io.emit('user count', currentUsers);
|
||||
```
|
||||
|
||||
Now, you can implement a way for your client to listen for this event! Similar to listening for a connection on the server, you will use the `on` keyword.
|
||||
|
||||
```js
|
||||
socket.on('user count', function(data) {
|
||||
console.log(data);
|
||||
});
|
||||
```
|
||||
|
||||
Now, try loading up your app, authenticate, and you should see in your client console '1' representing the current user count! Try loading more clients up, and authenticating to see the number go up.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#communicate-by-emitting-7" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
`currentUsers` should be defined.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/currentUsers/s,
|
||||
'You should have variable currentUsers defined'
|
||||
);
|
||||
```
|
||||
|
||||
Server should emit the current user count at each new connection.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/io.emit.*('|")user count('|").*currentUsers/s,
|
||||
'You should emit "user count" with data currentUsers'
|
||||
);
|
||||
```
|
||||
|
||||
Your client should be listening for `'user count'` event.
|
||||
|
||||
```js
|
||||
const url = new URL("/public/client.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user count('|")/s,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
```
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
---
|
||||
id: 589fc831f9fc0f352b528e76
|
||||
title: Handle a Disconnect
|
||||
challengeType: 2
|
||||
forumTopicId: 301552
|
||||
dashedName: handle-a-disconnect
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You may notice that up to now you have only been increasing the user count. Handling a user disconnecting is just as easy as handling the initial connect, except you have to listen for it on each socket instead of on the whole server.
|
||||
|
||||
To do this, add another listener inside the existing `'connect'` listener that listens for `'disconnect'` on the socket with no data passed through. You can test this functionality by just logging that a user has disconnected to the console.
|
||||
|
||||
```js
|
||||
socket.on('disconnect', () => {
|
||||
/*anything you want to do on disconnect*/
|
||||
});
|
||||
```
|
||||
|
||||
To make sure clients continuously have the updated count of current users, you should decrease `currentUsers` by 1 when the disconnect happens then emit the `'user count'` event with the updated count.
|
||||
|
||||
**Note:** Just like `'disconnect'`, all other events that a socket can emit to the server should be handled within the connecting listener where we have 'socket' defined.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#handle-a-disconnect-8" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Server should handle the event disconnect from a socket.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(data, /socket.on.*('|")disconnect('|")/s, '');
|
||||
```
|
||||
|
||||
Your client should be listening for `'user count'` event.
|
||||
|
||||
```js
|
||||
const url = new URL("/public/client.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user count('|")/s,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
```
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
---
|
||||
id: 589fc831f9fc0f352b528e77
|
||||
title: Authentication with Socket.IO
|
||||
challengeType: 2
|
||||
forumTopicId: 301548
|
||||
dashedName: authentication-with-socket-io
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Currently, you cannot determine who is connected to your web socket. While `req.user` contains the user object, that's only when your user interacts with the web server, and with web sockets you have no `req` (request) and therefore no user data. One way to solve the problem of knowing who is connected to your web socket is by parsing and decoding the cookie that contains the passport session then deserializing it to obtain the user object. Luckily, there is a package on NPM just for this that turns a once complex task into something simple!
|
||||
|
||||
`passport.socketio@~3.7.0`, `connect-mongo@~3.2.0`, and `cookie-parser@~1.4.5` have already been added as dependencies. Require them as `passportSocketIo`, `MongoStore`, and `cookieParser` respectively. Also, we need to initialize a new memory store, from `express-session` which we previously required. It should look like this:
|
||||
|
||||
```js
|
||||
const MongoStore = require('connect-mongo')(session);
|
||||
const URI = process.env.MONGO_URI;
|
||||
const store = new MongoStore({ url: URI });
|
||||
```
|
||||
|
||||
Now we just have to tell Socket.IO to use it and set the options. Be sure this is added before the existing socket code and not in the existing connection listener. For your server, it should look like this:
|
||||
|
||||
```js
|
||||
io.use(
|
||||
passportSocketIo.authorize({
|
||||
cookieParser: cookieParser,
|
||||
key: 'express.sid',
|
||||
secret: process.env.SESSION_SECRET,
|
||||
store: store,
|
||||
success: onAuthorizeSuccess,
|
||||
fail: onAuthorizeFail
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
Note that configuring Passport authentication for Socket.IO is very similar to the way we configured the `session` middleware for the API. This is because they are meant to use the same authentication method — get the session id from a cookie and validate it.
|
||||
|
||||
Previously, when we configured the `session` middleware, we didn't explicitly set the cookie name for session (`key`). This is because the `session` package was using the default value. Now that we've added another package which needs access to the same value from the cookies, we need to explicitly set the `key` value in both configuration objects.
|
||||
|
||||
Be sure to add the `key` with the cookie name to the `session` middleware that matches the Socket.IO key. Also, add the `store` reference to the options, near where we set `saveUninitialized: true`. This is necessary to tell Socket.IO which session to relate to.
|
||||
|
||||
<hr>
|
||||
|
||||
Now, define the `success`, and `fail` callback functions:
|
||||
|
||||
```js
|
||||
function onAuthorizeSuccess(data, accept) {
|
||||
console.log('successful connection to socket.io');
|
||||
|
||||
accept(null, true);
|
||||
}
|
||||
|
||||
function onAuthorizeFail(data, message, error, accept) {
|
||||
if (error) throw new Error(message);
|
||||
console.log('failed connection to socket.io:', message);
|
||||
accept(null, false);
|
||||
}
|
||||
```
|
||||
|
||||
The user object is now accessible on your socket object as `socket.request.user`. For example, now you can add the following:
|
||||
|
||||
```js
|
||||
console.log('user ' + socket.request.user.username + ' connected');
|
||||
```
|
||||
|
||||
It will log to the server console who has connected!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#authentication-with-socketio-9" target="_blank" rel="noopener noreferrer nofollow">check out the project up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
`passport.socketio` should be a dependency.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport.socketio',
|
||||
'Your project should list "passport.socketio" as a dependency'
|
||||
);
|
||||
```
|
||||
|
||||
`cookie-parser` should be a dependency.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'cookie-parser',
|
||||
'Your project should list "cookie-parser" as a dependency'
|
||||
);
|
||||
```
|
||||
|
||||
passportSocketIo should be properly required.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require\((['"])passport\.socketio\1\)/gi,
|
||||
'You should correctly require and instantiate "passport.socketio"'
|
||||
);
|
||||
```
|
||||
|
||||
passportSocketIo should be properly setup.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/io\.use\(\s*\w+\.authorize\(/,
|
||||
'You should register "passport.socketio" as socket.io middleware and provide it correct options'
|
||||
);
|
||||
```
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
---
|
||||
id: 589fc832f9fc0f352b528e78
|
||||
title: Announce New Users
|
||||
challengeType: 2
|
||||
forumTopicId: 301546
|
||||
dashedName: announce-new-users
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Many chat rooms are able to announce when a user connects or disconnects and then display that to all of the connected users in the chat. Seeing as though you already are emitting an event on connect and disconnect, you will just have to modify this event to support such a feature. The most logical way of doing so is sending 3 pieces of data with the event: the username of the user who connected/disconnected, the current user count, and if that username connected or disconnected.
|
||||
|
||||
Change the event name to `'user'`, and pass an object along containing the fields `username`, `currentUsers`, and `connected` (to be `true` in case of connection, or `false` for disconnection of the user sent). Be sure to change both `'user count'` events and set the disconnect one to send `false` for the field `connected` instead of `true` like the event emitted on connect.
|
||||
|
||||
```js
|
||||
io.emit('user', {
|
||||
username: socket.request.user.username,
|
||||
currentUsers,
|
||||
connected: true
|
||||
});
|
||||
```
|
||||
|
||||
Now your client will have all the necessary information to correctly display the current user count and announce when a user connects or disconnects! To handle this event on the client side we should listen for `'user'`, then update the current user count by using jQuery to change the text of `#num-users` to `'{NUMBER} users online'`, as well as append a `<li>` to the unordered list with id `messages` with `'{NAME} has {joined/left} the chat.'`.
|
||||
|
||||
An implementation of this could look like the following:
|
||||
|
||||
```js
|
||||
socket.on('user', data => {
|
||||
$('#num-users').text(data.currentUsers + ' users online');
|
||||
let message =
|
||||
data.username +
|
||||
(data.connected ? ' has joined the chat.' : ' has left the chat.');
|
||||
$('#messages').append($('<li>').html('<b>' + message + '</b>'));
|
||||
});
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135/3#announce-new-users-10" target="_blank" rel="noopener noreferrer nofollow">the project completed up to this point </a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Event `'user'` should be emitted with `username`, `currentUsers`, and `connected`.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
// Regex is lenient to match both `username` and `name` as the key on purpose.
|
||||
assert.match(
|
||||
data,
|
||||
/io.emit.*('|")user\1.*name.*currentUsers.*connected/s,
|
||||
'You should have an event emitted named user sending name, currentUsers, and connected'
|
||||
);
|
||||
```
|
||||
|
||||
Client should properly handle and display the new data from event `'user'`.
|
||||
|
||||
```js
|
||||
const url = new URL("/public/client.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user\1[^]*num-users/s,
|
||||
'You should change the text of "#num-users" within on your client within the "user" event listener to show the current users connected'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user\1[^]*messages.*li/s,
|
||||
'You should append a list item to "#messages" on your client within the "user" event listener to announce a user came or went'
|
||||
);
|
||||
```
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
---
|
||||
id: 589fc832f9fc0f352b528e79
|
||||
title: Send and Display Chat Messages
|
||||
challengeType: 2
|
||||
forumTopicId: 301562
|
||||
dashedName: send-and-display-chat-messages
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
It's time you start allowing clients to send a chat message to the server to emit to all the clients! In your `client.js` file, you should see there is already a block of code handling when the message form is submitted.
|
||||
|
||||
```js
|
||||
$('form').submit(function() {
|
||||
/*logic*/
|
||||
});
|
||||
```
|
||||
|
||||
Within the form submit code, you should emit an event after you define `messageToSend` but before you clear the text box `#m`. The event should be named `'chat message'` and the data should just be `messageToSend`.
|
||||
|
||||
```js
|
||||
socket.emit('chat message', messageToSend);
|
||||
```
|
||||
|
||||
Now, on your server, you should be listening to the socket for the event `'chat message'` with the data being named `message`. Once the event is received, it should emit the event `'chat message'` to all sockets using `io.emit`, sending a data object containing the `username` and `message`.
|
||||
|
||||
In `client.js`, you should now listen for event `'chat message'` and, when received, append a list item to `#messages` with the username, a colon, and the message!
|
||||
|
||||
At this point, the chat should be fully functional and sending messages across all clients!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#send-and-display-chat-messages-11" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Server should listen for `'chat message'` and then emit it properly.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")chat message('|")[^]*io.emit.*('|")chat message('|").*username.*message/s,
|
||||
'Your server should listen to the socket for "chat message" then emit to all users "chat message" with name and message in the data object'
|
||||
);
|
||||
```
|
||||
|
||||
Client should properly handle and display the new data from event `'chat message'`.
|
||||
|
||||
```js
|
||||
const url = new URL("/public/client.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")chat message('|")[^]*messages.*li/s,
|
||||
'You should append a list item to #messages on your client within the "chat message" event listener to display the new message'
|
||||
);
|
||||
```
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
---
|
||||
id: 58a25c98f9fc0f352b528e7f
|
||||
title: Hashing Your Passwords
|
||||
challengeType: 2
|
||||
forumTopicId: 301553
|
||||
dashedName: hashing-your-passwords
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Going back to the information security section, you may remember that storing plaintext passwords is *never* okay. Now it is time to implement BCrypt to solve this issue.
|
||||
|
||||
`bcrypt@~5.0.0` has already been added as a dependency, so require it in your server. You will need to handle hashing in 2 key areas: where you handle registering/saving a new account, and when you check to see that a password is correct on login.
|
||||
|
||||
Currently on your registration route, you insert a user's plaintext password into the database like so: `password: req.body.password`. Hash the passwords instead by adding the following before your database logic: `const hash = bcrypt.hashSync(req.body.password, 12);`, and replacing the `req.body.password` in the database saving with just `password: hash`.
|
||||
|
||||
On your authentication strategy, you check for the following in your code before completing the process: `if (password !== user.password) return done(null, false);`. After making the previous changes, now `user.password` is a hash. Before making a change to the existing code, notice how the statement is checking if the password is **not** equal then return non-authenticated. With this in mind, change that code to look as follows to properly check the password entered against the hash:
|
||||
|
||||
```js
|
||||
if (!bcrypt.compareSync(password, user.password)) {
|
||||
return done(null, false);
|
||||
}
|
||||
```
|
||||
|
||||
That is all it takes to implement one of the most important security features when you have to store passwords.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#hashing-your-passwords-1" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
BCrypt should be a dependency.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json()
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'bcrypt',
|
||||
'Your project should list "bcrypt" as a dependency'
|
||||
);
|
||||
```
|
||||
|
||||
BCrypt should be correctly required and implemented.
|
||||
|
||||
```js
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')bcrypt\1/gi,
|
||||
'You should have required bcrypt'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/bcrypt.hashSync/gi,
|
||||
'You should use hash the password in the registration'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/bcrypt.compareSync/gi,
|
||||
'You should compare the password to the hash in your strategy'
|
||||
);
|
||||
```
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 69ae9f3ca9e6da4eb0d5f7f9
|
||||
title: Getters and Setters
|
||||
challengeType: 11
|
||||
videoId: o8zKHbpd87s
|
||||
dashedName: getters-and-setters
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about getters and setters.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What is a setter?
|
||||
|
||||
## --answers--
|
||||
|
||||
A function that automatically deletes an object when it goes out of scope.
|
||||
|
||||
---
|
||||
|
||||
A method that retrieves or reads the value of an attribute.
|
||||
|
||||
---
|
||||
|
||||
A method in a class used to set or update the value of a private or protected attribute.
|
||||
|
||||
---
|
||||
|
||||
A method that creates a new class instance without modifying any attributes.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 69ae9f41a9e6da4eb0d5f7fa
|
||||
title: OOP Principles
|
||||
challengeType: 11
|
||||
videoId: rkOObSk6ApA
|
||||
dashedName: oop-principles
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about common OOP principles.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Which of the following is NOT a core OOP principle?
|
||||
|
||||
## --answers--
|
||||
|
||||
Polymorphism
|
||||
|
||||
---
|
||||
|
||||
Encapsulation
|
||||
|
||||
---
|
||||
|
||||
Singleton
|
||||
|
||||
---
|
||||
|
||||
Abstraction
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: 699e8ca68f18cf77dfce5c27
|
||||
title: Difference Quotient
|
||||
challengeType: 11
|
||||
videoId: D5ajV73gC1k
|
||||
dashedName: difference-quotient
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about the difference quotient.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What is a secant line?
|
||||
|
||||
## --answers--
|
||||
|
||||
A line that is always parallel to the x-axis.
|
||||
|
||||
---
|
||||
|
||||
A vertical line that intersects the y-axis.
|
||||
|
||||
---
|
||||
|
||||
A line that stretches between two points on the graph of a function.
|
||||
|
||||
---
|
||||
|
||||
A line that touches the graph of a function at exactly one point.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 699e85648f18cf77dfce5c23
|
||||
title: Ellipses
|
||||
challengeType: 11
|
||||
videoId: AwBM2AMwfu8
|
||||
dashedName: ellipses
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about ellipses, their standard equations, and how to identify their key features.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What is an ellipse?
|
||||
|
||||
## --answers--
|
||||
|
||||
The set of points such that the sum of the distances from two fixed points is constant.
|
||||
|
||||
---
|
||||
|
||||
The set of points such that the distance from one fixed point is constant.
|
||||
|
||||
---
|
||||
|
||||
The set of points such that the product of the distances from two fixed points is constant.
|
||||
|
||||
---
|
||||
|
||||
The set of points equidistant from a line and a point.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
1
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: 699e85688f18cf77dfce5c24
|
||||
title: Hyperbolas
|
||||
challengeType: 11
|
||||
videoId: rQnuydK6bBE
|
||||
dashedName: hyperbolas
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about hyperbolas.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What is a hyperbola?
|
||||
|
||||
## --answers--
|
||||
|
||||
The set of points such that the distance from one fixed point is constant.
|
||||
|
||||
---
|
||||
|
||||
A set of points such that the difference of the distances from two fixed points is constant.
|
||||
|
||||
---
|
||||
|
||||
The set of points such that the sum of the distances from two fixed points is constant.
|
||||
|
||||
---
|
||||
|
||||
The set of points equidistant from a line and a point.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
2
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: 699e85568f18cf77dfce5c20
|
||||
title: Law of Cosines - Old Version
|
||||
challengeType: 11
|
||||
videoId: 3BMPsNLtcmA
|
||||
dashedName: law-of-cosines-old-version
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will continue to learn about the Law of Cosines.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
How many forms can the Law of Cosines be written in?
|
||||
|
||||
## --answers--
|
||||
|
||||
6
|
||||
|
||||
---
|
||||
|
||||
1
|
||||
|
||||
---
|
||||
|
||||
2
|
||||
|
||||
---
|
||||
|
||||
3
|
||||
|
||||
## --video-solution--
|
||||
|
||||
4
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 699e85518f18cf77dfce5c1f
|
||||
title: Law of Cosines
|
||||
challengeType: 11
|
||||
videoId: KGF52g-s4Rs
|
||||
dashedName: law-of-cosines
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about the Law of Cosines.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What is the Law of Cosines?
|
||||
|
||||
## --answers--
|
||||
|
||||
For any triangle with sides of length a, b, and c, and angle C opposite side c, the Law of Cosines states that $c^2 = a^2 + b^2 + 2ab\cos(C)$.
|
||||
|
||||
---
|
||||
|
||||
For any triangle with sides of length a, b, and c, and angle C opposite side c, the Law of Cosines states that $c = a + b - 2\cos(C)$.
|
||||
|
||||
---
|
||||
|
||||
For any triangle with sides of length a, b, and c, and angle C opposite side c, the Law of Cosines states that $c^2 = a^2 + b^2 - 2ab\cos(C)$.
|
||||
|
||||
---
|
||||
|
||||
For any triangle with sides of length a, b, and c, and angle C opposite side c, the Law of Cosines states that $c = a \cdot b \cdot 2\cos(C)$.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 699e855a8f18cf77dfce5c21
|
||||
title: Law of Sines
|
||||
challengeType: 11
|
||||
videoId: Arm0baqLKTo
|
||||
dashedName: law-of-sines
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about the Law of Sines.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What can the Law of Sines be used to find?
|
||||
|
||||
## --answers--
|
||||
|
||||
To solve triangles that are right triangles.
|
||||
|
||||
---
|
||||
|
||||
To solve triangles that are not necessarily right triangles.
|
||||
|
||||
---
|
||||
|
||||
To solve triangles that are equilateral triangles.
|
||||
|
||||
---
|
||||
|
||||
To solve triangles that are isosceles triangles.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
2
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: 699e855e8f18cf77dfce5c22
|
||||
title: Parabolas - Vertex, Focus, Directrix
|
||||
challengeType: 11
|
||||
videoId: fAXpgprNFq0
|
||||
dashedName: parabolas-vertex-focus-directrix
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about special forms for equations of parabolas, and how to find the vertex, focus, and directrix of a parabola.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Which of the following is a valid form for the equation of a parabola?
|
||||
|
||||
## --answers--
|
||||
|
||||
$x - h = 4p(y - k)^2$
|
||||
|
||||
---
|
||||
|
||||
$y^2 - k^2 = 4p(x - h)$
|
||||
|
||||
---
|
||||
|
||||
$(y - k)^2 = 4p(x - h)$
|
||||
|
||||
---
|
||||
|
||||
$y - k = 4p(x - h)^2$
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 699e85718f18cf77dfce5c26
|
||||
title: Parametric Equations
|
||||
challengeType: 11
|
||||
videoId: VP5KJr2giwI
|
||||
dashedName: parametric-equations
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about parametric equations.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What is a cartesian equation for a curve?
|
||||
|
||||
## --answers--
|
||||
|
||||
An equation in terms of r and θ that describes a curve.
|
||||
|
||||
---
|
||||
|
||||
An equation in terms of x and y that describes a curve.
|
||||
|
||||
---
|
||||
|
||||
An equation that only gives the slope of a curve at each point.
|
||||
|
||||
---
|
||||
|
||||
An equation in terms of distance from a fixed point only.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
2
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: 699e856c8f18cf77dfce5c25
|
||||
title: Polar Coordinates
|
||||
challengeType: 11
|
||||
videoId: T8_EJkpyPvM
|
||||
dashedName: polar-coordinates
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about polar coordinates.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What are polar coordinates?
|
||||
|
||||
## --answers--
|
||||
|
||||
Coordinates represented as (θ, φ), where θ is the angle from the y-axis and φ is the angle from the x-axis.
|
||||
|
||||
---
|
||||
|
||||
Coordinates represented as (r, s), where r is the distance from the origin and s is the slope of the line from the origin.
|
||||
|
||||
---
|
||||
|
||||
Coordinates represented as (x, y), where x and y are distances along the horizontal and vertical axes.
|
||||
|
||||
---
|
||||
|
||||
Coordinates represented as (r, θ), where r is the distance from the origin and θ is the angle from the positive x-axis.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
4
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 699e854b8f18cf77dfce5c1e
|
||||
title: Solving Right Triangles
|
||||
challengeType: 11
|
||||
videoId: AkRnIZDdq04
|
||||
dashedName: solving-right-triangles
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn how to solve right triangles.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What does it mean to solve a right triangle?
|
||||
|
||||
## --answers--
|
||||
|
||||
To solve a right triangle means to find the area of the triangle only.
|
||||
|
||||
---
|
||||
|
||||
To solve a right triangle means to find only the length of the hypotenuse.
|
||||
|
||||
---
|
||||
|
||||
To solve a right triangle means to find the lengths of all sides and the measures of all angles.
|
||||
|
||||
---
|
||||
|
||||
To solve a right triangle means to draw the triangle without calculating any sides or angles.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: 698dcc2c7932b3cc4b19c927
|
||||
title: Binary Search in Code
|
||||
challengeType: 11
|
||||
videoId: mg7F5D8Wk5o
|
||||
dashedName: binary-search-in-code
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will code out the binary search algorithm using Python.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Why was the `while first <= last:` condition used in the solution?
|
||||
|
||||
## --answers--
|
||||
|
||||
To ensure only the first half of the array is ever checked.
|
||||
|
||||
---
|
||||
|
||||
To make the loop run forever until manually stopped.
|
||||
|
||||
---
|
||||
|
||||
To skip checking the last element in the search range.
|
||||
|
||||
---
|
||||
|
||||
To keep searching while there are still elements in the range and stop when the range is empty.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
4
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
---
|
||||
id: 698dca057932b3cc4b19c926
|
||||
title: Linear Search in Code
|
||||
challengeType: 11
|
||||
videoId: bKkgjdPkL3A
|
||||
dashedName: linear-search-in-code
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will code out the linear search algorithm using Python.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Which of the following is the correct way to write a linear search function?
|
||||
|
||||
## --answers--
|
||||
|
||||
```python
|
||||
def linear_search(list, target):
|
||||
for i in range(len(list) - 1):
|
||||
if list[i] == target:
|
||||
return i
|
||||
return -1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```python
|
||||
def linear_search(list, target):
|
||||
for i in range(len(list)):
|
||||
if i == target:
|
||||
return i
|
||||
return -1
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```python
|
||||
def linear_search(list, target):
|
||||
for i in range(len(list)):
|
||||
if list[i] == target:
|
||||
return i
|
||||
else:
|
||||
return -1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```python
|
||||
def linear_search(list, target):
|
||||
for i in range(len(list)):
|
||||
if list[i] == target:
|
||||
return i
|
||||
return None
|
||||
```
|
||||
|
||||
## --video-solution--
|
||||
|
||||
4
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: 698dce727932b3cc4b19c928
|
||||
title: Recursive Binary Search
|
||||
challengeType: 11
|
||||
videoId: RQNBzDoG_gA
|
||||
dashedName: recursive-binary-search
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn how to create a recursive solution for binary search.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What is a recursive function?
|
||||
|
||||
## --answers--
|
||||
|
||||
A function that performs a calculation only once.
|
||||
|
||||
---
|
||||
|
||||
A function that calls itself.
|
||||
|
||||
---
|
||||
|
||||
A function that never returns a value.
|
||||
|
||||
---
|
||||
|
||||
A function that can only run in a loop.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
2
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
id: 587d8259367417b2b2512c85
|
||||
title: Implement Selection Sort
|
||||
challengeType: 1
|
||||
forumTopicId: 301616
|
||||
dashedName: implement-selection-sort
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Here we will implement selection sort. Selection sort works by selecting the minimum value in a list and swapping it with the first value in the list. It then starts at the second position, selects the smallest value in the remaining list, and swaps it with the second element. It continues iterating through the list and swapping elements until it reaches the end of the list. Now the list is sorted. Selection sort has quadratic time complexity in all cases.
|
||||
|
||||
**Instructions**: Write a function `selectionSort` which takes an array of integers as input and returns an array of these integers in sorted order from least to greatest.
|
||||
|
||||
# --hints--
|
||||
|
||||
`selectionSort` should be a function.
|
||||
|
||||
```js
|
||||
assert.isFunction(selectionSort);
|
||||
```
|
||||
|
||||
`selectionSort` should return a sorted array (least to greatest).
|
||||
|
||||
```js
|
||||
function isSorted(a){
|
||||
for(let i = 0; i < a.length - 1; i++)
|
||||
if(a[i] > a[i + 1])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
assert.isTrue(
|
||||
isSorted(
|
||||
selectionSort([
|
||||
1,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
345,
|
||||
123,
|
||||
43,
|
||||
32,
|
||||
5643,
|
||||
63,
|
||||
123,
|
||||
43,
|
||||
2,
|
||||
55,
|
||||
1,
|
||||
234,
|
||||
92
|
||||
])
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`selectionSort([1,4,2,8,345,123,43,32,5643,63,123,43,2,55,1,234,92])` should return an array that is unchanged except for order.
|
||||
|
||||
```js
|
||||
assert.sameMembers(
|
||||
selectionSort([
|
||||
1,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
345,
|
||||
123,
|
||||
43,
|
||||
32,
|
||||
5643,
|
||||
63,
|
||||
123,
|
||||
43,
|
||||
2,
|
||||
55,
|
||||
1,
|
||||
234,
|
||||
92
|
||||
]),
|
||||
[1, 4, 2, 8, 345, 123, 43, 32, 5643, 63, 123, 43, 2, 55, 1, 234, 92]
|
||||
);
|
||||
```
|
||||
|
||||
`selectionSort` should not use the built-in `.sort()` method.
|
||||
|
||||
```js
|
||||
function isBuiltInSortUsed(){
|
||||
let sortUsed = false;
|
||||
const temp = Array.prototype.sort;
|
||||
Array.prototype.sort = () => sortUsed = true;
|
||||
try {
|
||||
selectionSort([0, 1]);
|
||||
} finally {
|
||||
Array.prototype.sort = temp;
|
||||
}
|
||||
return sortUsed;
|
||||
}
|
||||
assert.isFalse(isBuiltInSortUsed());
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
function selectionSort(array) {
|
||||
// Only change code below this line
|
||||
return array;
|
||||
// Only change code above this line
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
function selectionSort(array) {
|
||||
for (let i = 0; i < array.length-1; i++) {
|
||||
let minimumIndex = i;
|
||||
for (let j = i+1; j < array.length; j++){
|
||||
if (array[j] < array[minimumIndex]) {
|
||||
minimumIndex = j;
|
||||
}
|
||||
}
|
||||
let value = array[minimumIndex];
|
||||
array[minimumIndex] = array[i];
|
||||
array[i] = value;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
id: 587d8259367417b2b2512c86
|
||||
title: Implement Insertion Sort
|
||||
challengeType: 1
|
||||
forumTopicId: 301613
|
||||
dashedName: implement-insertion-sort
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The next sorting method we'll look at is insertion sort. This method works by building up a sorted array at the beginning of the list. It begins the sorted array with the first element. Then it inspects the next element and swaps it backwards into the sorted array until it is in sorted position. It continues iterating through the list and swapping new items backwards into the sorted portion until it reaches the end. This algorithm has quadratic time complexity in the average and worst cases.
|
||||
|
||||
**Instructions:** Write a function `insertionSort` which takes an array of integers as input and returns an array of these integers in sorted order from least to greatest.
|
||||
|
||||
# --hints--
|
||||
|
||||
`insertionSort` should be a function.
|
||||
|
||||
```js
|
||||
assert.isFunction(insertionSort);
|
||||
```
|
||||
|
||||
`insertionSort` should return a sorted array (least to greatest).
|
||||
|
||||
```js
|
||||
function isSorted(a){
|
||||
for(let i = 0; i < a.length - 1; i++)
|
||||
if(a[i] > a[i + 1])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
assert.isTrue(
|
||||
isSorted(
|
||||
insertionSort([
|
||||
1,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
345,
|
||||
123,
|
||||
43,
|
||||
32,
|
||||
5643,
|
||||
63,
|
||||
123,
|
||||
43,
|
||||
2,
|
||||
55,
|
||||
1,
|
||||
234,
|
||||
92
|
||||
])
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`insertionSort([1,4,2,8,345,123,43,32,5643,63,123,43,2,55,1,234,92])` should return an array that is unchanged except for order.
|
||||
|
||||
```js
|
||||
assert.sameMembers(
|
||||
insertionSort([
|
||||
1,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
345,
|
||||
123,
|
||||
43,
|
||||
32,
|
||||
5643,
|
||||
63,
|
||||
123,
|
||||
43,
|
||||
2,
|
||||
55,
|
||||
1,
|
||||
234,
|
||||
92
|
||||
]),
|
||||
[1, 4, 2, 8, 345, 123, 43, 32, 5643, 63, 123, 43, 2, 55, 1, 234, 92]
|
||||
);
|
||||
```
|
||||
|
||||
`insertionSort([5, 4, 33, 2, 8])` should return `[2, 4, 5, 8, 33]`.
|
||||
|
||||
```js
|
||||
assert.deepEqual(insertionSort([5, 4, 33, 2, 8]), [2, 4, 5, 8, 33])
|
||||
```
|
||||
|
||||
`insertionSort` should not use the built-in `.sort()` method.
|
||||
|
||||
```js
|
||||
function isBuiltInSortUsed(){
|
||||
let sortUsed = false;
|
||||
const temp = Array.prototype.sort;
|
||||
Array.prototype.sort = () => sortUsed = true;
|
||||
try {
|
||||
insertionSort([0, 1]);
|
||||
} finally {
|
||||
Array.prototype.sort = temp;
|
||||
}
|
||||
return sortUsed;
|
||||
}
|
||||
assert.isFalse(isBuiltInSortUsed());
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
function insertionSort(array) {
|
||||
// Only change code below this line
|
||||
return array;
|
||||
// Only change code above this line
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
function insertionSort (array) {
|
||||
for (let currentIndex = 0; currentIndex < array.length; currentIndex++) {
|
||||
let current = array[currentIndex];
|
||||
let j = currentIndex - 1;
|
||||
while (j > -1 && array[j] > current) {
|
||||
array[j + 1] = array[j];
|
||||
j--;
|
||||
}
|
||||
array[j + 1] = current;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
id: 587d825a367417b2b2512c89
|
||||
title: Implement Quick Sort
|
||||
challengeType: 1
|
||||
forumTopicId: 301615
|
||||
dashedName: implement-quick-sort
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Here we will move on to an intermediate sorting algorithm: quick sort. Quick sort is an efficient, recursive divide-and-conquer approach to sorting an array. In this method, a pivot value is chosen in the original array. The array is then partitioned into two subarrays of values less than and greater than the pivot value. We then combine the result of recursively calling the quick sort algorithm on both sub-arrays. This continues until the base case of an empty or single-item array is reached, which we return. The unwinding of the recursive calls return us the sorted array.
|
||||
|
||||
Quick sort is a very efficient sorting method, providing *O(nlog(n))* performance on average. It is also relatively easy to implement. These attributes make it a popular and useful sorting method.
|
||||
|
||||
**Instructions:** Write a function `quickSort` which takes an array of integers as input and returns an array of these integers in sorted order from least to greatest. While the choice of the pivot value is important, any pivot will do for our purposes here. For simplicity, the first or last element could be used.
|
||||
|
||||
# --hints--
|
||||
|
||||
`quickSort` should be a function.
|
||||
|
||||
```js
|
||||
assert(typeof quickSort == 'function');
|
||||
```
|
||||
|
||||
`quickSort` should return a sorted array (least to greatest).
|
||||
|
||||
```js
|
||||
function isSorted(a){
|
||||
for(let i = 0; i < a.length - 1; i++)
|
||||
if(a[i] > a[i + 1])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
assert.isTrue(
|
||||
isSorted(
|
||||
quickSort([
|
||||
1,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
345,
|
||||
123,
|
||||
43,
|
||||
32,
|
||||
5643,
|
||||
63,
|
||||
123,
|
||||
43,
|
||||
2,
|
||||
55,
|
||||
1,
|
||||
234,
|
||||
92
|
||||
])
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`quickSort([1,4,2,8,345,123,43,32,5643,63,123,43,2,55,1,234,92])` should return an array that is unchanged except for order.
|
||||
|
||||
```js
|
||||
assert.sameMembers(
|
||||
quickSort([
|
||||
1,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
345,
|
||||
123,
|
||||
43,
|
||||
32,
|
||||
5643,
|
||||
63,
|
||||
123,
|
||||
43,
|
||||
2,
|
||||
55,
|
||||
1,
|
||||
234,
|
||||
92
|
||||
]),
|
||||
[1, 4, 2, 8, 345, 123, 43, 32, 5643, 63, 123, 43, 2, 55, 1, 234, 92]
|
||||
);
|
||||
```
|
||||
|
||||
`quickSort` should not use the built-in `.sort()` method.
|
||||
|
||||
```js
|
||||
function isBuiltInSortUsed(){
|
||||
let sortUsed = false;
|
||||
const temp = Array.prototype.sort;
|
||||
Array.prototype.sort = () => sortUsed = true;
|
||||
try {
|
||||
quickSort([0, 1]);
|
||||
} finally {
|
||||
Array.prototype.sort = temp;
|
||||
}
|
||||
return sortUsed;
|
||||
}
|
||||
assert.isFalse(isBuiltInSortUsed());
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
function quickSort(array) {
|
||||
// Only change code below this line
|
||||
return array;
|
||||
// Only change code above this line
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
function quickSort(array) {
|
||||
if (array.length === 0) {
|
||||
return [];
|
||||
} else {
|
||||
const pivotValue = array[0];
|
||||
|
||||
// Sort elements into three piles
|
||||
let lesser = [];
|
||||
let equal = [];
|
||||
let greater = [];
|
||||
for (let e of array) {
|
||||
if (e < pivotValue) {
|
||||
lesser.push(e);
|
||||
} else if (e > pivotValue) {
|
||||
greater.push(e);
|
||||
} else {
|
||||
equal.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
return [...quickSort(lesser), ...equal, ...quickSort(greater)];
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,155 @@
|
||||
---
|
||||
id: 587d825c367417b2b2512c8f
|
||||
title: Implement Merge Sort
|
||||
challengeType: 1
|
||||
forumTopicId: 301614
|
||||
dashedName: implement-merge-sort
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Another common intermediate sorting algorithm is merge sort. Like quick sort, merge sort also uses a divide-and-conquer, recursive methodology to sort an array. It takes advantage of the fact that it is relatively easy to sort two arrays as long as each is sorted in the first place. But we'll start with only one array as input, so how do we get to two sorted arrays from that? Well, we can recursively divide the original input in two until we reach the base case of an array with one item. A single-item array is naturally sorted, so then we can start combining. This combination will unwind the recursive calls that split the original array, eventually producing a final sorted array of all the elements. The steps of merge sort, then, are:
|
||||
|
||||
**1)** Recursively split the input array in half until a sub-array with only one element is produced.
|
||||
|
||||
**2)** Merge each sorted sub-array together to produce the final sorted array.
|
||||
|
||||
Merge sort is an efficient sorting method, with time complexity of *O(nlog(n))*. This algorithm is popular because it is performant and relatively easy to implement.
|
||||
|
||||
As an aside, this will be the last sorting algorithm we cover here. However, later in the section on tree data structures we will describe heap sort, another efficient sorting method that requires a binary heap in its implementation.
|
||||
|
||||
**Instructions:** Write a function `mergeSort` which takes an array of integers as input and returns an array of these integers in sorted order from least to greatest. A good way to implement this is to write one function, for instance `merge`, which is responsible for merging two sorted arrays, and another function, for instance `mergeSort`, which is responsible for the recursion that produces single-item arrays to feed into merge. Good luck!
|
||||
|
||||
# --hints--
|
||||
|
||||
`mergeSort` should be a function.
|
||||
|
||||
```js
|
||||
assert.isFunction(mergeSort);
|
||||
```
|
||||
|
||||
`mergeSort` should return a sorted array (least to greatest).
|
||||
|
||||
```js
|
||||
function isSorted(a){
|
||||
for(let i = 0; i < a.length - 1; i++)
|
||||
if(a[i] > a[i + 1])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
assert.isTrue(
|
||||
isSorted(
|
||||
mergeSort([
|
||||
1,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
345,
|
||||
123,
|
||||
43,
|
||||
32,
|
||||
5643,
|
||||
63,
|
||||
123,
|
||||
43,
|
||||
2,
|
||||
55,
|
||||
1,
|
||||
234,
|
||||
92
|
||||
])
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`mergeSort([1,4,2,8,345,123,43,32,5643,63,123,43,2,55,1,234,92])` should return an array that is unchanged except for order.
|
||||
|
||||
```js
|
||||
assert.sameMembers(
|
||||
mergeSort([
|
||||
1,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
345,
|
||||
123,
|
||||
43,
|
||||
32,
|
||||
5643,
|
||||
63,
|
||||
123,
|
||||
43,
|
||||
2,
|
||||
55,
|
||||
1,
|
||||
234,
|
||||
92
|
||||
]),
|
||||
[1, 4, 2, 8, 345, 123, 43, 32, 5643, 63, 123, 43, 2, 55, 1, 234, 92]
|
||||
);
|
||||
```
|
||||
|
||||
`mergeSort` should not use the built-in `.sort()` method.
|
||||
|
||||
```js
|
||||
function isBuiltInSortUsed(){
|
||||
let sortUsed = false;
|
||||
const temp = Array.prototype.sort;
|
||||
Array.prototype.sort = () => sortUsed = true;
|
||||
try {
|
||||
mergeSort([0, 1]);
|
||||
} finally {
|
||||
Array.prototype.sort = temp;
|
||||
}
|
||||
return sortUsed;
|
||||
}
|
||||
assert.isFalse(isBuiltInSortUsed());
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
function mergeSort(array) {
|
||||
// Only change code below this line
|
||||
return array;
|
||||
// Only change code above this line
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
function mergeSort(array) {
|
||||
if (array.length === 1) {
|
||||
return array;
|
||||
} else {
|
||||
const splitIndex = Math.floor(array.length / 2);
|
||||
return merge(
|
||||
mergeSort(array.slice(0, splitIndex)),
|
||||
mergeSort(array.slice(splitIndex))
|
||||
);
|
||||
}
|
||||
|
||||
// Merge two sorted arrays
|
||||
function merge(array1, array2) {
|
||||
let merged = [];
|
||||
while (array1.length && array2.length) {
|
||||
if (array1[0] < array2[0]) {
|
||||
merged.push(array1.shift());
|
||||
} else if (array1[0] > array2[0]) {
|
||||
merged.push(array2.shift());
|
||||
} else {
|
||||
merged.push(array1.shift(), array2.shift());
|
||||
}
|
||||
}
|
||||
|
||||
// After looping ends, one array is empty, and other array contains only
|
||||
// values greater than all values in `merged`
|
||||
return [...merged, ...array1, ...array2];
|
||||
}
|
||||
}
|
||||
|
||||
mergeSort([1, 4, 2, 8, 345, 123, 43, 32, 5643, 63, 123, 43, 2, 55, 1, 234, 92]);
|
||||
```
|
||||
@@ -0,0 +1,175 @@
|
||||
---
|
||||
id: 61abc7ebf3029b56226de5b6
|
||||
title: Implement Binary Search
|
||||
challengeType: 1
|
||||
forumTopicId: 487618
|
||||
dashedName: implement-binary-search
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Binary search is an **O(log(n))** efficiency algorithm for searching a sorted array to find an element. It operates using the following steps:
|
||||
|
||||
1. Find the middle `value` of a sorted array. If `value == target` return `true` (The value has been found and the search is complete).
|
||||
1. If middle `value < target`, search right half of array in next compare.
|
||||
1. If middle `value > target`, search left half of array in next compare.
|
||||
1. If after searching the whole array the value is not present, return `false` (The array has been searched and the value is not in the array).
|
||||
|
||||
As you can see, you are successively halving an array, which gives you the log(n) efficiency. For this challenge, we want you to show your work - how you got to the target value... the path you took!
|
||||
|
||||
# --instructions--
|
||||
|
||||
Write a function `binarySearch` that implements the binary search algorithm on an array, returning the path you took (each middle value comparison) to find the target in an array.
|
||||
|
||||
The function takes a sorted array of integers and a target value as input. It returns an array containing (in-order) the middle value you found at each halving of the original array until you found the target value. The target value should be the last element of the returned array. If the value is not found, return the string `Value Not Found`.
|
||||
|
||||
For example, `binarySearch([1,2,3,4,5,6,7], 5)` would return `[4,6,5]`.
|
||||
|
||||
For this challenge, when halving, you MUST use `Math.floor()` when doing division: `Math.floor(x/2)`. This will give a consistent, testable path.
|
||||
|
||||
**Note:** The following array will be used in tests:
|
||||
|
||||
```js
|
||||
const testArray = [
|
||||
0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 49, 70
|
||||
];
|
||||
```
|
||||
|
||||
# --hints--
|
||||
|
||||
`binarySearch` should be a function.
|
||||
|
||||
```js
|
||||
assert.isFunction(binarySearch);
|
||||
```
|
||||
|
||||
`binarySearch(testArray, 0)` should return `[13, 5, 2, 0]`.
|
||||
|
||||
```js
|
||||
const _testArray = [
|
||||
0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 49, 70
|
||||
];
|
||||
assert.deepEqual(binarySearch(_testArray, 0), [13, 5, 2, 0]);
|
||||
```
|
||||
|
||||
`binarySearch(testArray, 1)` should return `[13, 5, 2, 0, 1]`.
|
||||
|
||||
```js
|
||||
const _testArray = [
|
||||
0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 49, 70
|
||||
];
|
||||
assert.deepEqual(binarySearch(_testArray, 1), [13, 5, 2, 0, 1]);
|
||||
```
|
||||
|
||||
|
||||
`binarySearch(testArray, 2)` should return `[13, 5, 2]`.
|
||||
|
||||
```js
|
||||
const _testArray = [
|
||||
0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 49, 70
|
||||
];
|
||||
assert.deepEqual(binarySearch(_testArray, 2), [13, 5, 2]);
|
||||
```
|
||||
|
||||
`binarySearch(testArray, 6)` should return the string `Value Not Found`.
|
||||
|
||||
```js
|
||||
const _testArray = [
|
||||
0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 49, 70
|
||||
];
|
||||
assert.strictEqual(binarySearch(_testArray, 6), 'Value Not Found');
|
||||
```
|
||||
|
||||
`binarySearch(testArray, 11)` should return `[13, 5, 10, 11]`.
|
||||
|
||||
```js
|
||||
const _testArray = [
|
||||
0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 49, 70
|
||||
];
|
||||
assert.deepEqual(binarySearch(_testArray, 11), [13, 5, 10, 11])
|
||||
```
|
||||
|
||||
`binarySearch(testArray, 13)` should return `[13]`.
|
||||
|
||||
```js
|
||||
const _testArray = [
|
||||
0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 49, 70
|
||||
];
|
||||
assert.deepEqual(binarySearch(_testArray, 13), [13]);
|
||||
```
|
||||
|
||||
`binarySearch(testArray, 70)` should return `[13, 19, 22, 49, 70]`.
|
||||
|
||||
```js
|
||||
const _testArray = [
|
||||
0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 49, 70
|
||||
];
|
||||
assert.deepEqual(binarySearch(_testArray, 70), [13, 19, 22, 49, 70]);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
function binarySearch(searchList, value) {
|
||||
let arrayPath = [];
|
||||
return arrayPath;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
let binarySearch = (searchList, value) => {
|
||||
let arrayPath = [];
|
||||
|
||||
// set initial L - M - R
|
||||
let left = 0;
|
||||
let right = searchList.length - 1;
|
||||
let middle = Math.floor(right / 2);
|
||||
|
||||
// if first comparison finds value
|
||||
if (searchList[middle] == value) {
|
||||
arrayPath.push(searchList[middle]);
|
||||
return arrayPath;
|
||||
}
|
||||
|
||||
while (searchList[middle] !== value) {
|
||||
// add to output array
|
||||
arrayPath.push(searchList[middle]);
|
||||
|
||||
// not found
|
||||
if (right < left) {
|
||||
return 'Value Not Found';
|
||||
}
|
||||
// value is in left or right portion of array
|
||||
// update L - M - R
|
||||
if (searchList[middle] > value) {
|
||||
right = middle - 1;
|
||||
middle = left + Math.floor((right - left) / 2);
|
||||
} else {
|
||||
left = middle + 1;
|
||||
middle = left + Math.floor((right - left) / 2);
|
||||
}
|
||||
|
||||
// if found update output array and exit
|
||||
if (searchList[middle] == value) {
|
||||
arrayPath.push(searchList[middle]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return arrayPath;
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
id: 8d5123c8c441eddfaeb5bdef
|
||||
title: Implement Bubble Sort
|
||||
challengeType: 1
|
||||
forumTopicId: 301612
|
||||
dashedName: implement-bubble-sort
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
This is the first of several challenges on sorting algorithms. Given an array of unsorted items, we want to be able to return a sorted array. We will see several different methods to do this and learn some tradeoffs between these different approaches. While most modern languages have built-in sorting methods for operations like this, it is still important to understand some of the common basic approaches and learn how they can be implemented.
|
||||
|
||||
Here we will see bubble sort. The bubble sort method starts at the beginning of an unsorted array and 'bubbles up' unsorted values towards the end, iterating through the array until it is completely sorted. It does this by comparing adjacent items and swapping them if they are out of order. The method continues looping through the array until no swaps occur at which point the array is sorted.
|
||||
|
||||
This method requires multiple iterations through the array and for average and worst cases has quadratic time complexity. While simple, it is usually impractical in most situations.
|
||||
|
||||
**Instructions:** Write a function `bubbleSort` which takes an array of integers as input and returns an array of these integers in sorted order from least to greatest.
|
||||
|
||||
# --hints--
|
||||
|
||||
`bubbleSort` should be a function.
|
||||
|
||||
```js
|
||||
assert.isFunction(bubbleSort);
|
||||
```
|
||||
|
||||
`bubbleSort` should return a sorted array (least to greatest).
|
||||
|
||||
```js
|
||||
function isSorted(a){
|
||||
for(let i = 0; i < a.length - 1; i++)
|
||||
if(a[i] > a[i + 1])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
assert.isTrue(
|
||||
isSorted(
|
||||
bubbleSort([
|
||||
1,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
345,
|
||||
123,
|
||||
43,
|
||||
32,
|
||||
5643,
|
||||
63,
|
||||
123,
|
||||
43,
|
||||
2,
|
||||
55,
|
||||
1,
|
||||
234,
|
||||
92
|
||||
])
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`bubbleSort([1,4,2,8,345,123,43,32,5643,63,123,43,2,55,1,234,92])` should return an array that is unchanged except for order.
|
||||
|
||||
```js
|
||||
assert.sameMembers(
|
||||
bubbleSort([
|
||||
1,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
345,
|
||||
123,
|
||||
43,
|
||||
32,
|
||||
5643,
|
||||
63,
|
||||
123,
|
||||
43,
|
||||
2,
|
||||
55,
|
||||
1,
|
||||
234,
|
||||
92
|
||||
]),
|
||||
[1, 4, 2, 8, 345, 123, 43, 32, 5643, 63, 123, 43, 2, 55, 1, 234, 92]
|
||||
);
|
||||
```
|
||||
|
||||
`bubbleSort` should not use the built-in `.sort()` method.
|
||||
|
||||
```js
|
||||
function isBuiltInSortUsed(){
|
||||
let sortUsed = false;
|
||||
const temp = Array.prototype.sort;
|
||||
Array.prototype.sort = () => sortUsed = true;
|
||||
try {
|
||||
bubbleSort([0, 1]);
|
||||
} finally {
|
||||
Array.prototype.sort = temp;
|
||||
}
|
||||
return sortUsed;
|
||||
}
|
||||
assert.isFalse(isBuiltInSortUsed());
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
function bubbleSort(array) {
|
||||
// Only change code below this line
|
||||
return array;
|
||||
// Only change code above this line
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
function bubbleSort(array) {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
let swapped = false;
|
||||
for (let j = 1; j < array.length; j++) {
|
||||
if (array[j - 1] > array[j]) {
|
||||
let temp = array[j-1];
|
||||
array[j-1] = array[j];
|
||||
array[j] = temp;
|
||||
swapped = true;
|
||||
}
|
||||
}
|
||||
if (swapped === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
id: a3f503de51cf954ede28891d
|
||||
title: Find the Symmetric Difference
|
||||
challengeType: 1
|
||||
forumTopicId: 301611
|
||||
dashedName: find-the-symmetric-difference
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The mathematical term <dfn>symmetric difference</dfn> (`△` or `⊕`) of two sets is the set of elements which are in either of the two sets but not in both. For example, for sets `A = {1, 2, 3}` and `B = {2, 3, 4}`, `A △ B = {1, 4}`.
|
||||
|
||||
Symmetric difference is a binary operation, which means it operates on only two elements. So to evaluate an expression involving symmetric differences among *three* elements (`A △ B △ C`), you must complete one operation at a time. Thus, for sets `A` and `B` above, and `C = {2, 3}`, `A △ B △ C = (A △ B) △ C = {1, 4} △ {2, 3} = {1, 2, 3, 4}`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Create a function that takes two or more arrays and returns an array of their symmetric difference. The returned array must contain only unique values (*no duplicates*).
|
||||
|
||||
# --hints--
|
||||
|
||||
`sym([1, 2, 3], [5, 2, 1, 4])` should return `[3, 4, 5]`.
|
||||
|
||||
```js
|
||||
assert.sameMembers(sym([1, 2, 3], [5, 2, 1, 4]), [3, 4, 5]);
|
||||
```
|
||||
|
||||
`sym([1, 2, 3], [5, 2, 1, 4])` should contain only three elements.
|
||||
|
||||
```js
|
||||
assert.equal(sym([1, 2, 3], [5, 2, 1, 4]).length, 3);
|
||||
```
|
||||
|
||||
`sym([1, 2, 3, 3], [5, 2, 1, 4])` should return `[3, 4, 5]`.
|
||||
|
||||
```js
|
||||
assert.sameMembers(sym([1, 2, 3, 3], [5, 2, 1, 4]), [3, 4, 5]);
|
||||
```
|
||||
|
||||
`sym([1, 2, 3, 3], [5, 2, 1, 4])` should contain only three elements.
|
||||
|
||||
```js
|
||||
assert.equal(sym([1, 2, 3, 3], [5, 2, 1, 4]).length, 3);
|
||||
```
|
||||
|
||||
`sym([1, 2, 3], [5, 2, 1, 4, 5])` should return `[3, 4, 5]`.
|
||||
|
||||
```js
|
||||
assert.sameMembers(sym([1, 2, 3], [5, 2, 1, 4, 5]), [3, 4, 5]);
|
||||
```
|
||||
|
||||
`sym([1, 2, 3], [5, 2, 1, 4, 5])` should contain only three elements.
|
||||
|
||||
```js
|
||||
assert.equal(sym([1, 2, 3], [5, 2, 1, 4, 5]).length, 3);
|
||||
```
|
||||
|
||||
`sym([1, 2, 5], [2, 3, 5], [3, 4, 5])` should return `[1, 4, 5]`.
|
||||
|
||||
```js
|
||||
assert.sameMembers(sym([1, 2, 5], [2, 3, 5], [3, 4, 5]), [1, 4, 5]);
|
||||
```
|
||||
|
||||
`sym([1, 2, 5], [2, 3, 5], [3, 4, 5])` should contain only three elements.
|
||||
|
||||
```js
|
||||
assert.equal(sym([1, 2, 5], [2, 3, 5], [3, 4, 5]).length, 3);
|
||||
```
|
||||
|
||||
`sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5])` should return `[1, 4, 5]`.
|
||||
|
||||
```js
|
||||
assert.sameMembers(sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]), [1, 4, 5]);
|
||||
```
|
||||
|
||||
`sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5])` should contain only three elements.
|
||||
|
||||
```js
|
||||
assert.equal(sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]).length, 3);
|
||||
```
|
||||
|
||||
`sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3])` should return `[2, 3, 4, 6, 7]`.
|
||||
|
||||
```js
|
||||
assert.sameMembers(
|
||||
sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3]),
|
||||
[2, 3, 4, 6, 7]
|
||||
);
|
||||
```
|
||||
|
||||
`sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3])` should contain only five elements.
|
||||
|
||||
```js
|
||||
assert.equal(
|
||||
sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3]).length,
|
||||
5
|
||||
);
|
||||
```
|
||||
|
||||
`sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3], [5, 3, 9, 8], [1])` should return `[1, 2, 4, 5, 6, 7, 8, 9]`.
|
||||
|
||||
```js
|
||||
assert.sameMembers(
|
||||
sym(
|
||||
[3, 3, 3, 2, 5],
|
||||
[2, 1, 5, 7],
|
||||
[3, 4, 6, 6],
|
||||
[1, 2, 3],
|
||||
[5, 3, 9, 8],
|
||||
[1]
|
||||
),
|
||||
[1, 2, 4, 5, 6, 7, 8, 9]
|
||||
);
|
||||
```
|
||||
|
||||
`sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3], [5, 3, 9, 8], [1])` should contain only eight elements.
|
||||
|
||||
```js
|
||||
assert.equal(
|
||||
sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3], [5, 3, 9, 8], [1])
|
||||
.length,
|
||||
8
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
function sym(args) {
|
||||
return args;
|
||||
}
|
||||
|
||||
sym([1, 2, 3], [5, 2, 1, 4]);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
function sym() {
|
||||
var arrays = [].slice.call(arguments);
|
||||
return arrays.reduce(function (symDiff, arr) {
|
||||
return symDiff.concat(arr).filter(function (val, idx, theArr) {
|
||||
return theArr.indexOf(val) === idx
|
||||
&& (symDiff.indexOf(val) === -1 || arr.indexOf(val) === -1);
|
||||
});
|
||||
});
|
||||
}
|
||||
sym([1, 2, 3], [5, 2, 1, 4]);
|
||||
```
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
id: a3f503de51cfab748ff001aa
|
||||
title: Pairwise
|
||||
challengeType: 1
|
||||
forumTopicId: 301617
|
||||
dashedName: pairwise
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Given an array `arr`, find element pairs whose sum equal the second argument `arg` and return the sum of their indices.
|
||||
|
||||
You may use multiple pairs that have the same numeric elements but different indices. Each pair should use the lowest possible available indices. Once an element has been used it cannot be reused to pair with another element. For instance, `pairwise([1, 1, 2], 3)` creates a pair `[2, 1]` using the 1 at index 0 rather than the 1 at index 1, because 0+2 < 1+2.
|
||||
|
||||
For example `pairwise([7, 9, 11, 13, 15], 20)` returns `6`. The pairs that sum to 20 are `[7, 13]` and `[9, 11]`. We can then write out the array with their indices and values.
|
||||
|
||||
<div style='margin-left: 2em;'>
|
||||
|
||||
| Index | 0 | 1 | 2 | 3 | 4 |
|
||||
| ----- | - | - | -- | -- | -- |
|
||||
| Value | 7 | 9 | 11 | 13 | 15 |
|
||||
|
||||
</div>
|
||||
|
||||
Below we'll take their corresponding indices and add them.
|
||||
|
||||
<div style='margin-left: 2em;'>
|
||||
|
||||
7 + 13 = 20 → Indices 0 + 3 = 3
|
||||
9 + 11 = 20 → Indices 1 + 2 = 3
|
||||
3 + 3 = 6 → Return `6`
|
||||
|
||||
</div>
|
||||
|
||||
# --hints--
|
||||
|
||||
`pairwise([1, 4, 2, 3, 0, 5], 7)` should return 11.
|
||||
|
||||
```js
|
||||
assert.deepEqual(pairwise([1, 4, 2, 3, 0, 5], 7), 11);
|
||||
```
|
||||
|
||||
`pairwise([1, 3, 2, 4], 4)` should return 1.
|
||||
|
||||
```js
|
||||
assert.deepEqual(pairwise([1, 3, 2, 4], 4), 1);
|
||||
```
|
||||
|
||||
`pairwise([1, 1, 1], 2)` should return 1.
|
||||
|
||||
```js
|
||||
assert.deepEqual(pairwise([1, 1, 1], 2), 1);
|
||||
```
|
||||
|
||||
`pairwise([0, 0, 0, 0, 1, 1], 1)` should return 10.
|
||||
|
||||
```js
|
||||
assert.deepEqual(pairwise([0, 0, 0, 0, 1, 1], 1), 10);
|
||||
```
|
||||
|
||||
`pairwise([], 100)` should return 0.
|
||||
|
||||
```js
|
||||
assert.deepEqual(pairwise([], 100), 0);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
function pairwise(arr, arg) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
pairwise([1,4,2,3,0,5], 7);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
function pairwise(arr, arg) {
|
||||
var sum = 0;
|
||||
arr.forEach(function(e, i, a) {
|
||||
if (e != null) {
|
||||
var diff = arg-e;
|
||||
a[i] = null;
|
||||
var dix = a.indexOf(diff);
|
||||
if (dix !== -1) {
|
||||
sum += dix;
|
||||
sum += i;
|
||||
a[dix] = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
return sum;
|
||||
}
|
||||
|
||||
pairwise([1,4,2,3,0,5], 7);
|
||||
```
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
id: a56138aff60341a09ed6c480
|
||||
title: Inventory Update
|
||||
challengeType: 1
|
||||
forumTopicId: 16019
|
||||
dashedName: inventory-update
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Compare and update the inventory stored in a 2D array against a second 2D array of a fresh delivery. Update the current existing inventory item quantities (in `arr1`). If an item cannot be found, add the new item and quantity into the inventory array. The returned inventory array should be in alphabetical order by item.
|
||||
|
||||
# --hints--
|
||||
|
||||
The function `updateInventory` should return an array.
|
||||
|
||||
```js
|
||||
assert.isArray(
|
||||
updateInventory(
|
||||
[
|
||||
[21, 'Bowling Ball'],
|
||||
[2, 'Dirty Sock'],
|
||||
[1, 'Hair Pin'],
|
||||
[5, 'Microphone']
|
||||
],
|
||||
[
|
||||
[2, 'Hair Pin'],
|
||||
[3, 'Half-Eaten Apple'],
|
||||
[67, 'Bowling Ball'],
|
||||
[7, 'Toothpaste']
|
||||
]
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
`updateInventory([[21, "Bowling Ball"], [2, "Dirty Sock"], [1, "Hair Pin"], [5, "Microphone"]], [[2, "Hair Pin"], [3, "Half-Eaten Apple"], [67, "Bowling Ball"], [7, "Toothpaste"]])` should return an array with a length of 6.
|
||||
|
||||
```js
|
||||
assert.equal(
|
||||
updateInventory(
|
||||
[
|
||||
[21, 'Bowling Ball'],
|
||||
[2, 'Dirty Sock'],
|
||||
[1, 'Hair Pin'],
|
||||
[5, 'Microphone']
|
||||
],
|
||||
[
|
||||
[2, 'Hair Pin'],
|
||||
[3, 'Half-Eaten Apple'],
|
||||
[67, 'Bowling Ball'],
|
||||
[7, 'Toothpaste']
|
||||
]
|
||||
).length,
|
||||
6
|
||||
);
|
||||
```
|
||||
|
||||
`updateInventory([[21, "Bowling Ball"], [2, "Dirty Sock"], [1, "Hair Pin"], [5, "Microphone"]], [[2, "Hair Pin"], [3, "Half-Eaten Apple"], [67, "Bowling Ball"], [7, "Toothpaste"]])` should return `[[88, "Bowling Ball"], [2, "Dirty Sock"], [3, "Hair Pin"], [3, "Half-Eaten Apple"], [5, "Microphone"], [7, "Toothpaste"]]`.
|
||||
|
||||
```js
|
||||
assert.deepEqual(
|
||||
updateInventory(
|
||||
[
|
||||
[21, 'Bowling Ball'],
|
||||
[2, 'Dirty Sock'],
|
||||
[1, 'Hair Pin'],
|
||||
[5, 'Microphone']
|
||||
],
|
||||
[
|
||||
[2, 'Hair Pin'],
|
||||
[3, 'Half-Eaten Apple'],
|
||||
[67, 'Bowling Ball'],
|
||||
[7, 'Toothpaste']
|
||||
]
|
||||
),
|
||||
[
|
||||
[88, 'Bowling Ball'],
|
||||
[2, 'Dirty Sock'],
|
||||
[3, 'Hair Pin'],
|
||||
[3, 'Half-Eaten Apple'],
|
||||
[5, 'Microphone'],
|
||||
[7, 'Toothpaste']
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
`updateInventory([[21, "Bowling Ball"], [2, "Dirty Sock"], [1, "Hair Pin"], [5, "Microphone"]], [])` should return `[[21, "Bowling Ball"], [2, "Dirty Sock"], [1, "Hair Pin"], [5, "Microphone"]]`.
|
||||
|
||||
```js
|
||||
assert.deepEqual(
|
||||
updateInventory(
|
||||
[
|
||||
[21, 'Bowling Ball'],
|
||||
[2, 'Dirty Sock'],
|
||||
[1, 'Hair Pin'],
|
||||
[5, 'Microphone']
|
||||
],
|
||||
[]
|
||||
),
|
||||
[
|
||||
[21, 'Bowling Ball'],
|
||||
[2, 'Dirty Sock'],
|
||||
[1, 'Hair Pin'],
|
||||
[5, 'Microphone']
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
`updateInventory([], [[2, "Hair Pin"], [3, "Half-Eaten Apple"], [67, "Bowling Ball"], [7, "Toothpaste"]])` should return `[[67, "Bowling Ball"], [2, "Hair Pin"], [3, "Half-Eaten Apple"], [7, "Toothpaste"]]`.
|
||||
|
||||
```js
|
||||
assert.deepEqual(
|
||||
updateInventory(
|
||||
[],
|
||||
[
|
||||
[2, 'Hair Pin'],
|
||||
[3, 'Half-Eaten Apple'],
|
||||
[67, 'Bowling Ball'],
|
||||
[7, 'Toothpaste']
|
||||
]
|
||||
),
|
||||
[
|
||||
[67, 'Bowling Ball'],
|
||||
[2, 'Hair Pin'],
|
||||
[3, 'Half-Eaten Apple'],
|
||||
[7, 'Toothpaste']
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
`updateInventory([[0, "Bowling Ball"], [0, "Dirty Sock"], [0, "Hair Pin"], [0, "Microphone"]], [[1, "Hair Pin"], [1, "Half-Eaten Apple"], [1, "Bowling Ball"], [1, "Toothpaste"]])` should return `[[1, "Bowling Ball"], [0, "Dirty Sock"], [1, "Hair Pin"], [1, "Half-Eaten Apple"], [0, "Microphone"], [1, "Toothpaste"]]`.
|
||||
|
||||
```js
|
||||
assert.deepEqual(
|
||||
updateInventory(
|
||||
[
|
||||
[0, 'Bowling Ball'],
|
||||
[0, 'Dirty Sock'],
|
||||
[0, 'Hair Pin'],
|
||||
[0, 'Microphone']
|
||||
],
|
||||
[
|
||||
[1, 'Hair Pin'],
|
||||
[1, 'Half-Eaten Apple'],
|
||||
[1, 'Bowling Ball'],
|
||||
[1, 'Toothpaste']
|
||||
]
|
||||
),
|
||||
[
|
||||
[1, 'Bowling Ball'],
|
||||
[0, 'Dirty Sock'],
|
||||
[1, 'Hair Pin'],
|
||||
[1, 'Half-Eaten Apple'],
|
||||
[0, 'Microphone'],
|
||||
[1, 'Toothpaste']
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
function updateInventory(arr1, arr2) {
|
||||
return arr1;
|
||||
}
|
||||
|
||||
// Example inventory lists
|
||||
var curInv = [
|
||||
[21, "Bowling Ball"],
|
||||
[2, "Dirty Sock"],
|
||||
[1, "Hair Pin"],
|
||||
[5, "Microphone"]
|
||||
];
|
||||
|
||||
var newInv = [
|
||||
[2, "Hair Pin"],
|
||||
[3, "Half-Eaten Apple"],
|
||||
[67, "Bowling Ball"],
|
||||
[7, "Toothpaste"]
|
||||
];
|
||||
|
||||
updateInventory(curInv, newInv);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
function updateInventory(arr1, arr2) {
|
||||
arr2.forEach(function(item) {
|
||||
createOrUpdate(arr1, item);
|
||||
});
|
||||
// All inventory must be accounted for or you're fired!
|
||||
return arr1;
|
||||
}
|
||||
|
||||
function createOrUpdate(arr1, item) {
|
||||
var index = -1;
|
||||
while (++index < arr1.length) {
|
||||
if (arr1[index][1] === item[1]) {
|
||||
arr1[index][0] += item[0];
|
||||
return;
|
||||
}
|
||||
if (arr1[index][1] > item[1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
arr1.splice(index, 0, item);
|
||||
}
|
||||
|
||||
// Example inventory lists
|
||||
var curInv = [
|
||||
[21, 'Bowling Ball'],
|
||||
[2, 'Dirty Sock'],
|
||||
[1, 'Hair Pin'],
|
||||
[5, 'Microphone']
|
||||
];
|
||||
|
||||
var newInv = [
|
||||
[2, 'Hair Pin'],
|
||||
[3, 'Half-Eaten Apple'],
|
||||
[67, 'Bowling Ball'],
|
||||
[7, 'Toothpaste']
|
||||
];
|
||||
|
||||
updateInventory(curInv, newInv);
|
||||
```
|
||||
@@ -0,0 +1,126 @@
|
||||
---
|
||||
id: a7bf700cd123b9a54eef01d5
|
||||
title: No Repeats Please
|
||||
challengeType: 1
|
||||
forumTopicId: 16037
|
||||
dashedName: no-repeats-please
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Return the number of total permutations of the provided string that don't have repeated consecutive letters. Assume that all characters in the provided string are each unique.
|
||||
|
||||
For example, `aab` should return 2 because it has 6 total permutations (`aab`, `aab`, `aba`, `aba`, `baa`, `baa`), but only 2 of them (`aba` and `aba`) don't have the same letter (in this case `a`) repeating.
|
||||
|
||||
# --hints--
|
||||
|
||||
`permAlone("aab")` should return a number.
|
||||
|
||||
```js
|
||||
assert.isNumber(permAlone('aab'));
|
||||
```
|
||||
|
||||
`permAlone("aab")` should return 2.
|
||||
|
||||
```js
|
||||
assert.strictEqual(permAlone('aab'), 2);
|
||||
```
|
||||
|
||||
`permAlone("aaa")` should return 0.
|
||||
|
||||
```js
|
||||
assert.strictEqual(permAlone('aaa'), 0);
|
||||
```
|
||||
|
||||
`permAlone("aabb")` should return 8.
|
||||
|
||||
```js
|
||||
assert.strictEqual(permAlone('aabb'), 8);
|
||||
```
|
||||
|
||||
`permAlone("abcdefa")` should return 3600.
|
||||
|
||||
```js
|
||||
assert.strictEqual(permAlone('abcdefa'), 3600);
|
||||
```
|
||||
|
||||
`permAlone("abfdefa")` should return 2640.
|
||||
|
||||
```js
|
||||
assert.strictEqual(permAlone('abfdefa'), 2640);
|
||||
```
|
||||
|
||||
`permAlone("zzzzzzzz")` should return 0.
|
||||
|
||||
```js
|
||||
assert.strictEqual(permAlone('zzzzzzzz'), 0);
|
||||
```
|
||||
|
||||
`permAlone("a")` should return 1.
|
||||
|
||||
```js
|
||||
assert.strictEqual(permAlone('a'), 1);
|
||||
```
|
||||
|
||||
`permAlone("aaab")` should return 0.
|
||||
|
||||
```js
|
||||
assert.strictEqual(permAlone('aaab'), 0);
|
||||
```
|
||||
|
||||
`permAlone("aaabb")` should return 12.
|
||||
|
||||
```js
|
||||
assert.strictEqual(permAlone('aaabb'), 12);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
function permAlone(str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
permAlone('aab');
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
function permAlone(str) {
|
||||
return permuter(str).filter(function(perm) {
|
||||
return !perm.match(/(.)\1/g);
|
||||
}).length;
|
||||
}
|
||||
|
||||
function permuter(str) {
|
||||
// http://staff.roguecc.edu/JMiller/JavaScript/permute.html
|
||||
//permArr: Global array which holds the list of permutations
|
||||
//usedChars: Global utility array which holds a list of "currently-in-use" characters
|
||||
var permArr = [], usedChars = [];
|
||||
function permute(input) {
|
||||
//convert input into a char array (one element for each character)
|
||||
var i, ch, chars = input.split("");
|
||||
for (i = 0; i < chars.length; i++) {
|
||||
//get and remove character at index "i" from char array
|
||||
ch = chars.splice(i, 1);
|
||||
//add removed character to the end of used characters
|
||||
usedChars.push(ch);
|
||||
//when there are no more characters left in char array to add, add used chars to list of permutations
|
||||
if (chars.length === 0) permArr[permArr.length] = usedChars.join("");
|
||||
//send characters (minus the removed one from above) from char array to be permuted
|
||||
permute(chars.join(""));
|
||||
//add removed character back into char array in original position
|
||||
chars.splice(i, 0, ch);
|
||||
//remove the last character used off the end of used characters array
|
||||
usedChars.pop();
|
||||
}
|
||||
}
|
||||
permute(str);
|
||||
return permArr;
|
||||
}
|
||||
|
||||
permAlone('aab');
|
||||
```
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: 699e54288f18cf77dfce5c0a
|
||||
title: Angles and Their Measures
|
||||
challengeType: 11
|
||||
videoId: Ycu5Xn5rFuc
|
||||
dashedName: angles-and-their-measures
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about the different ways to describe angles.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
How are angles usually measured?
|
||||
|
||||
## --answers--
|
||||
|
||||
They are measured by lines or curves.
|
||||
|
||||
---
|
||||
|
||||
They are measured by degrees or radians.
|
||||
|
||||
---
|
||||
|
||||
They are measured by feet or meters.
|
||||
|
||||
---
|
||||
|
||||
They are measured by miles or kilometers.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
2
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: 699e56b58f18cf77dfce5c0b
|
||||
title: Arclength and Areas of Sectors
|
||||
challengeType: 11
|
||||
videoId: hNtpuLx5i6Y
|
||||
dashedName: arclength-and-areas-of-sectors
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about arcs, sectors and areas of sectors.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What is a sector?
|
||||
|
||||
## --answers--
|
||||
|
||||
A wedge of pi for a circle.
|
||||
|
||||
---
|
||||
|
||||
A line that cuts a circle into two equal halves.
|
||||
|
||||
---
|
||||
|
||||
The distance around the outside of a circle.
|
||||
|
||||
---
|
||||
|
||||
The center point of a circle.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
1
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 699e56ba8f18cf77dfce5c0c
|
||||
title: Linear and Radial Speed
|
||||
challengeType: 11
|
||||
videoId: nsZvoNqWCMo
|
||||
dashedName: linear-and-radial-speed
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this video, you will learn about linear and radial speed for rotating circles.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What is linear speed?
|
||||
|
||||
## --answers--
|
||||
|
||||
The number of rotations completed in one minute.
|
||||
|
||||
---
|
||||
|
||||
The distance from the center of a circle to its edge.
|
||||
|
||||
---
|
||||
|
||||
The distance a point travels in a unit of time.
|
||||
|
||||
---
|
||||
|
||||
The angle a point rotates through in a unit of time.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
---
|
||||
id: 587d774c367417b2b2512a9c
|
||||
title: Add a Text Alternative to Images for Visually Impaired Accessibility
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cPp7VfD'
|
||||
forumTopicId: 16628
|
||||
dashedName: add-a-text-alternative-to-images-for-visually-impaired-accessibility
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You've likely seen an `alt` attribute on an `img` tag in other challenges. `alt` text describes the image's content and provides a text-alternative for it. An `alt` attribute helps in cases where the image fails to load or can't be seen by a user. Search engines also use it to understand what an image contains to include it in search results. Here's an example:
|
||||
|
||||
```html
|
||||
<img src="importantLogo.jpeg" alt="Company logo">
|
||||
```
|
||||
|
||||
People with visual impairments rely on screen readers to convert web content to an audio interface. They won't get information if it's only presented visually. For images, screen readers can access the `alt` attribute and read its contents to deliver key information.
|
||||
|
||||
Good `alt` text provides the reader a brief description of the image. You should always include an `alt` attribute on your image. Per HTML5 specification, this is now considered mandatory.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat happens to be both a coding ninja and an actual ninja, who is building a website to share his knowledge. The profile picture he wants to use shows his skills and should be appreciated by all site visitors. Add an `alt` attribute in the `img` tag, that explains Camper Cat is doing karate. (The image `src` doesn't link to an actual file, so you should see the `alt` text in the display.)
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `img` tag should have an `alt` attribute and it should not be empty.
|
||||
|
||||
```js
|
||||
assert.isNotEmpty(document.querySelector('img')?.getAttribute('alt'));
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<img src="doingKarateWow.jpeg">
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<img src="doingKarateWow.jpeg" alt="Someone doing karate">
|
||||
```
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
---
|
||||
id: 587d774c367417b2b2512a9d
|
||||
title: Know When Alt Text Should be Left Blank
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cM9P4t2'
|
||||
forumTopicId: 301019
|
||||
dashedName: know-when-alt-text-should-be-left-blank
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In the last challenge, you learned that including an `alt` attribute when using `img` tags is mandatory. However, sometimes images are grouped with a caption already describing them, or are used for decoration only. In these cases, `alt` text may seem redundant or unnecessary.
|
||||
|
||||
When an image is already explained with text content or does not add meaning to a page, the `img` still needs an `alt` attribute, but it can be set to an empty string. Here's an example:
|
||||
|
||||
```html
|
||||
<img src="visualDecoration.jpeg" alt="">
|
||||
```
|
||||
|
||||
Background images usually fall under the 'decorative' label as well. However, they are typically applied with CSS rules, and therefore not part of the markup screen readers process.
|
||||
|
||||
**Note:** For images with a caption, you may still want to include `alt` text since it helps search engines catalog the image's content.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat has coded a skeleton page for the blog part of his website. He's planning to add a visual break between his two articles with a decorative image of a samurai sword. Add an `alt` attribute to the `img` tag and set it to an empty string. (Note that the image `src` doesn't link to an actual file - don't worry that there are no swords showing in the display.)
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `img` tag should have an `alt` attribute.
|
||||
|
||||
```js
|
||||
assert.isTrue(document.querySelector('img')?.hasAttribute('alt'));
|
||||
```
|
||||
|
||||
The `alt` attribute should be set to an empty string.
|
||||
|
||||
```js
|
||||
assert.isEmpty(document.querySelector('img')?.getAttribute('alt'));
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
<article>
|
||||
<h2>Defeating your Foe: the Red Dot is Ours!</h2>
|
||||
<p>To Come...</p>
|
||||
</article>
|
||||
|
||||
<img src="samuraiSwords.jpeg">
|
||||
|
||||
<article>
|
||||
<h2>Is Chuck Norris a Cat Person?</h2>
|
||||
<p>To Come...</p>
|
||||
</article>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
<article>
|
||||
<h2>Defeating your Foe: the Red Dot is Ours!</h2>
|
||||
<p>To Come...</p>
|
||||
</article>
|
||||
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
|
||||
<article>
|
||||
<h2>Is Chuck Norris a Cat Person?</h2>
|
||||
<p>To Come...</p>
|
||||
</article>
|
||||
```
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
---
|
||||
id: 587d774d367417b2b2512a9e
|
||||
title: Use Headings to Show Hierarchical Relationships of Content
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cqVEktm'
|
||||
forumTopicId: 301026
|
||||
dashedName: use-headings-to-show-hierarchical-relationships-of-content
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Headings (`h1` through `h6` elements) are workhorse tags that help provide structure and labeling to your content. Screen readers can be set to read only the headings on a page so the user gets a summary. This means it is important for the heading tags in your markup to have semantic meaning and relate to each other, not be picked merely for their size values.
|
||||
|
||||
*Semantic meaning* means that the tag you use around content indicates the type of information it contains.
|
||||
|
||||
If you were writing a paper with an introduction, a body, and a conclusion, it wouldn't make much sense to put the conclusion as a subsection of the body in your outline. It should be its own section. Similarly, the heading tags in a webpage need to go in order and indicate the hierarchical relationships of your content.
|
||||
|
||||
Headings with equal (or higher) rank start new implied sections, headings with lower rank start subsections of the previous one.
|
||||
|
||||
As an example, a page with an `h2` element followed by several subsections labeled with `h4` elements would confuse a screen reader user. With six choices, it's tempting to use a tag because it looks better in a browser, but you can use CSS to edit the relative sizing.
|
||||
|
||||
One final point, each page should always have one (and only one) `h1` element, which is the main subject of your content. This and the other headings are used in part by search engines to understand the topic of the page.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat wants a page on his site dedicated to becoming a ninja. Help him fix the headings so his markup gives semantic meaning to the content, and shows the proper parent-child relationships of his sections. Change all the `h5` tags to the proper heading level to indicate they are subsections of the `h2` ones. Use `h3` tags for the purpose.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should have 6 `h3` elements.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('h3') , 6);
|
||||
```
|
||||
|
||||
Your code should have 6 `h3` closing tags.
|
||||
|
||||
```js
|
||||
assert.lengthOf((code.match(/\/h3/g) || []) ,6);
|
||||
```
|
||||
|
||||
Your code should not have any `h5` elements.
|
||||
|
||||
```js
|
||||
assert.isEmpty(document.querySelectorAll('h5'));
|
||||
```
|
||||
|
||||
Your code should not have any `h5` closing tags.
|
||||
|
||||
```js
|
||||
assert.notMatch(code, /\/h5/);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<h1>How to Become a Ninja</h1>
|
||||
<main>
|
||||
<h2>Learn the Art of Moving Stealthily</h2>
|
||||
<h5>How to Hide in Plain Sight</h5>
|
||||
<h5>How to Climb a Wall</h5>
|
||||
|
||||
<h2>Learn the Art of Battle</h2>
|
||||
<h5>How to Strengthen your Body</h5>
|
||||
<h5>How to Fight like a Ninja</h5>
|
||||
|
||||
<h2>Learn the Art of Living with Honor</h2>
|
||||
<h5>How to Breathe Properly</h5>
|
||||
<h5>How to Simplify your Life</h5>
|
||||
</main>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<h1>How to Become a Ninja</h1>
|
||||
<main>
|
||||
<h2>Learn the Art of Moving Stealthily</h2>
|
||||
<h3>How to Hide in Plain Sight</h3>
|
||||
<h3>How to Climb a Wall</h3>
|
||||
|
||||
<h2>Learn the Art of Battle</h2>
|
||||
<h3>How to Strengthen your Body</h3>
|
||||
<h3>How to Fight like a Ninja</h3>
|
||||
|
||||
<h2>Learn the Art of Living with Honor</h2>
|
||||
<h3>How to Breathe Properly</h3>
|
||||
<h3>How to Simplify your Life</h3>
|
||||
</main>
|
||||
```
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
---
|
||||
id: 587d774e367417b2b2512a9f
|
||||
title: Jump Straight to the Content Using the main Element
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cPp7zuE'
|
||||
forumTopicId: 301018
|
||||
dashedName: jump-straight-to-the-content-using-the-main-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
HTML5 introduced several new elements that give developers more options while also incorporating accessibility features. These tags include `main`, `header`, `footer`, `nav`, `article`, and `section`, among others.
|
||||
|
||||
By default, a browser renders these elements similar to the humble `div`. However, using them where appropriate gives additional meaning to your markup. The tag name alone can indicate the type of information it contains, which adds semantic meaning to that content. Assistive technologies can access this information to provide better page summary or navigation options to their users.
|
||||
|
||||
The `main` element is used to wrap (you guessed it) the main content, and there should be only one per page. It's meant to surround the information related to your page's central topic. It's not meant to include items that repeat across pages, like navigation links or banners.
|
||||
|
||||
The `main` tag also has an embedded landmark feature that assistive technology can use to navigate to the main content quickly. If you've ever seen a "Jump to Main Content" link at the top of a page, using the `main` tag automatically gives assistive devices that functionality.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat has some big ideas for his ninja weapons page. Help him set up his markup by adding opening and closing `main` tags between the `header` and `footer` (covered in other challenges). Keep the `main` tags empty for now.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should have one `main` tag.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('main'),1);
|
||||
```
|
||||
|
||||
The `main` tags should be between the closing `header` tag and the opening `footer` tag.
|
||||
|
||||
```js
|
||||
assert.match(code,/<\/header>\s*?<main>\s*?<\/main>/gi);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<header>
|
||||
<h1>Weapons of the Ninja</h1>
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
<footer></footer>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<header>
|
||||
<h1>Weapons of the Ninja</h1>
|
||||
</header>
|
||||
<main>
|
||||
|
||||
</main>
|
||||
<footer></footer>
|
||||
```
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
---
|
||||
id: 587d774e367417b2b2512aa0
|
||||
title: Wrap Content in the article Element
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cPp79S3'
|
||||
forumTopicId: 301029
|
||||
dashedName: wrap-content-in-the-article-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
`article` is another one of the new HTML5 elements that add semantic meaning to your markup. `article` is a sectioning element and is used to wrap independent, self-contained content. The tag works well with blog entries, forum posts, or news articles.
|
||||
|
||||
Determining whether content can stand alone is usually a judgment call, but you can use a couple of simple tests. Ask yourself if you removed all surrounding context, would that content still make sense? Similarly, for text, would the content hold up if it were in an RSS feed?
|
||||
|
||||
Remember that folks using assistive technologies rely on organized, semantically meaningful markup to better understand your work.
|
||||
|
||||
**Note:** The `section` element is also new with HTML5, and has a slightly different semantic meaning than `article`. An `article` is for standalone content, and a `section` is for grouping thematically related content. They can be used within each other, as needed. For example, if a book is the `article`, then each chapter is a `section`. When there's no relationship between groups of content, then use a `div`.
|
||||
|
||||
`<div>` - groups content
|
||||
`<section>` - groups related content
|
||||
`<article>` - groups independent, self-contained content
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat used `article` tags to wrap the posts on his blog page, but he forgot to use them around the top one. Change the `div` tag to use an `article` tag instead.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should have three `article` tags.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('article') ,3);
|
||||
```
|
||||
|
||||
Your code should not have any `div` tags.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('div'), 0);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
<main>
|
||||
<div>
|
||||
<h2>The Garfield Files: Lasagna as Training Fuel?</h2>
|
||||
<p>The internet is littered with varying opinions on nutritional paradigms, from catnip paleo to hairball cleanses. But let's turn our attention to an often overlooked fitness fuel, and examine the protein-carb-NOM trifecta that is lasagna...</p>
|
||||
</div>
|
||||
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
|
||||
<article>
|
||||
<h2>Defeating your Foe: the Red Dot is Ours!</h2>
|
||||
<p>Felines the world over have been waging war on the most persistent of foes. This red nemesis combines both cunning stealth and lightning speed. But chin up, fellow fighters, our time for victory may soon be near...</p>
|
||||
</article>
|
||||
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
|
||||
<article>
|
||||
<h2>Is Chuck Norris a Cat Person?</h2>
|
||||
<p>Chuck Norris is widely regarded as the premier martial artist on the planet, and it's a complete coincidence that anyone who disagrees with this fact mysteriously disappears soon after. But the real question is, is he a cat person?...</p>
|
||||
</article>
|
||||
</main>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
<main>
|
||||
<article>
|
||||
<h2>The Garfield Files: Lasagna as Training Fuel?</h2>
|
||||
<p>The internet is littered with varying opinions on nutritional paradigms, from catnip paleo to hairball cleanses. But let's turn our attention to an often overlooked fitness fuel, and examine the protein-carb-NOM trifecta that is lasagna...</p>
|
||||
</article>
|
||||
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
|
||||
<article>
|
||||
<h2>Defeating your Foe: the Red Dot is Ours!</h2>
|
||||
<p>Felines the world over have been waging war on the most persistent of foes. This red nemesis combines both cunning stealth and lightning speed. But chin up, fellow fighters, our time for victory may soon be near...</p>
|
||||
</article>
|
||||
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
|
||||
<article>
|
||||
<h2>Is Chuck Norris a Cat Person?</h2>
|
||||
<p>Chuck Norris is widely regarded as the premier martial artist on the planet, and it's a complete coincidence that anyone who disagrees with this fact mysteriously disappears soon after. But the real question is, is he a cat person?...</p>
|
||||
</article>
|
||||
</main>
|
||||
```
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
---
|
||||
id: 587d7787367417b2b2512aa1
|
||||
title: Make Screen Reader Navigation Easier with the header Landmark
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cB76vtv'
|
||||
forumTopicId: 301023
|
||||
dashedName: make-screen-reader-navigation-easier-with-the-header-landmark
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The next HTML5 element that adds semantic meaning and improves accessibility is the `header` tag. It's used to wrap introductory information or navigation links for its parent tag and works well around content that's repeated at the top on multiple pages.
|
||||
|
||||
`header` shares the embedded landmark feature you saw with `main`, allowing assistive technologies to quickly navigate to that content.
|
||||
|
||||
**Note:** The `header` is meant for use in the `body` tag of your HTML document. It is different than the `head` element, which contains the page's title, meta information, etc.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat is writing some great articles about ninja training, and wants to add a page for them to his site. Change the top `div` that currently contains the `h1` to a `header` tag instead.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should have one `header` tag.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('header'),1);
|
||||
```
|
||||
|
||||
Your `header` tags should wrap around the `h1`.
|
||||
|
||||
```js
|
||||
const header = document.querySelector('header');
|
||||
const children = header?.querySelectorAll(`:scope ${'h1'}`);
|
||||
assert.lengthOf(children , 1);
|
||||
```
|
||||
|
||||
Your code should not have any `div` tags.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('div') , 0);
|
||||
```
|
||||
|
||||
Your `header` element should have a closing tag.
|
||||
|
||||
```js
|
||||
assert.isTrue(code.match(/<\/header>/g)?.length === code.match(/<header>/g)?.length);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
|
||||
<div>
|
||||
<h1>Training with Camper Cat</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<main>
|
||||
<section id="stealth">
|
||||
<h2>Stealth & Agility Training</h2>
|
||||
<article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
|
||||
<article><h3>No training is NP-complete without parkour</h3></article>
|
||||
</section>
|
||||
<section id="combat">
|
||||
<h2>Combat Training</h2>
|
||||
<article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
|
||||
<article><h3>Goodbye world: 5 proven ways to knock out an opponent</h3></article>
|
||||
</section>
|
||||
<section id="weapons">
|
||||
<h2>Weapons Training</h2>
|
||||
<article><h3>Swords: the best tool to literally divide and conquer</h3></article>
|
||||
<article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<h1>Training with Camper Cat</h1>
|
||||
</header>
|
||||
|
||||
|
||||
<main>
|
||||
<section id="stealth">
|
||||
<h2>Stealth & Agility Training</h2>
|
||||
<article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
|
||||
<article><h3>No training is NP-complete without parkour</h3></article>
|
||||
</section>
|
||||
<section id="combat">
|
||||
<h2>Combat Training</h2>
|
||||
<article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
|
||||
<article><h3>Goodbye world: 5 proven ways to knock out an opponent</h3></article>
|
||||
</section>
|
||||
<section id="weapons">
|
||||
<h2>Weapons Training</h2>
|
||||
<article><h3>Swords: the best tool to literally divide and conquer</h3></article>
|
||||
<article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
```
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
---
|
||||
id: 587d7788367417b2b2512aa2
|
||||
title: Make Screen Reader Navigation Easier with the nav Landmark
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/czVwWSv'
|
||||
forumTopicId: 301024
|
||||
dashedName: make-screen-reader-navigation-easier-with-the-nav-landmark
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The `nav` element is another HTML5 item with the embedded landmark feature for easy screen reader navigation. This tag is meant to wrap around the main navigation links in your page.
|
||||
|
||||
If there are repeated site links at the bottom of the page, it isn't necessary to markup those with a `nav` tag as well. Using a `footer` (covered in the next challenge) is sufficient.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat included navigation links at the top of his training page, but wrapped them in a `div`. Change the `div` to a `nav` tag to improve the accessibility on his page.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should have one `nav` tag.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('nav') , 1);
|
||||
```
|
||||
|
||||
Your `nav` tags should wrap around the `ul` and its list items.
|
||||
|
||||
```js
|
||||
const nav = document.querySelector('nav');
|
||||
const children = nav?.querySelectorAll(`:scope ${'ul'}`);
|
||||
assert.lengthOf(children,1);
|
||||
```
|
||||
|
||||
Your code should not have any `div` tags.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('div') , 0);
|
||||
```
|
||||
|
||||
Your `nav` element should have a closing tag.
|
||||
|
||||
```js
|
||||
assert.isTrue(code.match(/<\/nav>/g)?.length === code.match(/<nav>/g)?.length);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Training with Camper Cat</h1>
|
||||
|
||||
<div>
|
||||
<ul>
|
||||
<li><a href="#stealth">Stealth & Agility</a></li>
|
||||
<li><a href="#combat">Combat</a></li>
|
||||
<li><a href="#weapons">Weapons</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
<main>
|
||||
<section id="stealth">
|
||||
<h2>Stealth & Agility Training</h2>
|
||||
<article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
|
||||
<article><h3>No training is NP-complete without parkour</h3></article>
|
||||
</section>
|
||||
<section id="combat">
|
||||
<h2>Combat Training</h2>
|
||||
<article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
|
||||
<article><h3>Goodbye world: 5 proven ways to knock out an opponent</h3></article>
|
||||
</section>
|
||||
<section id="weapons">
|
||||
<h2>Weapons Training</h2>
|
||||
<article><h3>Swords: the best tool to literally divide and conquer</h3></article>
|
||||
<article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Training with Camper Cat</h1>
|
||||
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#stealth">Stealth & Agility</a></li>
|
||||
<li><a href="#combat">Combat</a></li>
|
||||
<li><a href="#weapons">Weapons</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</header>
|
||||
<main>
|
||||
<section id="stealth">
|
||||
<h2>Stealth & Agility Training</h2>
|
||||
<article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
|
||||
<article><h3>No training is NP-complete without parkour</h3></article>
|
||||
</section>
|
||||
<section id="combat">
|
||||
<h2>Combat Training</h2>
|
||||
<article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
|
||||
<article><h3>Goodbye world: 5 proven ways to knock out an opponent</h3></article>
|
||||
</section>
|
||||
<section id="weapons">
|
||||
<h2>Weapons Training</h2>
|
||||
<article><h3>Swords: the best tool to literally divide and conquer</h3></article>
|
||||
<article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
```
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
---
|
||||
id: 587d7788367417b2b2512aa3
|
||||
title: Make Screen Reader Navigation Easier with the footer Landmark
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/crVrDh8'
|
||||
forumTopicId: 301022
|
||||
dashedName: make-screen-reader-navigation-easier-with-the-footer-landmark
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Similar to `header` and `nav`, the `footer` element has a built-in landmark feature that allows assistive devices to quickly navigate to it. It's primarily used to contain copyright information or links to related documents that usually sit at the bottom of a page.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat's training page is making good progress. Change the `div` he used to wrap his copyright information at the bottom of the page to a `footer` element.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should have one `footer` tag.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('footer') ,1);
|
||||
```
|
||||
|
||||
Your code should not have any `div` tags.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('div'), 0);
|
||||
```
|
||||
|
||||
Your code should have an opening and closing `footer` tag.
|
||||
|
||||
```js
|
||||
assert.match(code,/<footer>\s*© 2018 Camper Cat\s*<\/footer>/g);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Training</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#stealth">Stealth & Agility</a></li>
|
||||
<li><a href="#combat">Combat</a></li>
|
||||
<li><a href="#weapons">Weapons</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<section id="stealth">
|
||||
<h2>Stealth & Agility Training</h2>
|
||||
<article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
|
||||
<article><h3>No training is NP-complete without parkour</h3></article>
|
||||
</section>
|
||||
<section id="combat">
|
||||
<h2>Combat Training</h2>
|
||||
<article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
|
||||
<article><h3>Goodbye world: 5 proven ways to knock out an opponent</h3></article>
|
||||
</section>
|
||||
<section id="weapons">
|
||||
<h2>Weapons Training</h2>
|
||||
<article><h3>Swords: the best tool to literally divide and conquer</h3></article>
|
||||
<article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
|
||||
<div>© 2018 Camper Cat</div>
|
||||
|
||||
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Training</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#stealth">Stealth & Agility</a></li>
|
||||
<li><a href="#combat">Combat</a></li>
|
||||
<li><a href="#weapons">Weapons</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<section id="stealth">
|
||||
<h2>Stealth & Agility Training</h2>
|
||||
<article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
|
||||
<article><h3>No training is NP-complete without parkour</h3></article>
|
||||
</section>
|
||||
<section id="combat">
|
||||
<h2>Combat Training</h2>
|
||||
<article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
|
||||
<article><h3>Goodbye world: 5 proven ways to knock out an opponent</h3></article>
|
||||
</section>
|
||||
<section id="weapons">
|
||||
<h2>Weapons Training</h2>
|
||||
<article><h3>Swords: the best tool to literally divide and conquer</h3></article>
|
||||
<article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
|
||||
|
||||
</body>
|
||||
```
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
---
|
||||
id: 587d7789367417b2b2512aa4
|
||||
title: Improve Accessibility of Audio Content with the audio Element
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cVJVkcZ'
|
||||
forumTopicId: 301014
|
||||
dashedName: improve-accessibility-of-audio-content-with-the-audio-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
HTML5's `audio` element gives semantic meaning when it wraps sound or audio stream content in your markup. Audio content also needs a text alternative to be accessible to people who are deaf or hard of hearing. This can be done with nearby text on the page or a link to a transcript.
|
||||
|
||||
The `audio` tag supports the `controls` attribute. This shows the browser default play, pause, and other controls, and supports keyboard functionality. This is a boolean attribute, meaning it doesn't need a value, its presence on the tag turns the setting on.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```html
|
||||
<audio id="meowClip" controls>
|
||||
<source src="audio/meow.mp3" type="audio/mpeg">
|
||||
<source src="audio/meow.ogg" type="audio/ogg">
|
||||
</audio>
|
||||
```
|
||||
|
||||
**Note:** Multimedia content usually has both visual and auditory components. It needs synchronized captions and a transcript so users with visual and/or auditory impairments can access it. Generally, a web developer is not responsible for creating the captions or transcript, but needs to know to include them.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Time to take a break from Camper Cat and meet fellow camper Zersiax (@zersiax), a champion of accessibility and a screen reader user. To hear a clip of his screen reader in action, add an `audio` element after the `p` element. Include the `controls` attribute. Then place a `source` element inside the `audio` tags with the `src` attribute set to `https://cdn.freecodecamp.org/curriculum/applied-accessibility/screen-reader.mp3` and `type` attribute set to `"audio/mpeg"`.
|
||||
|
||||
**Note:** The audio clip may sound fast and be difficult to understand, but that is a normal speed for screen reader users.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should have one `audio` tag.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('audio'),1);
|
||||
```
|
||||
|
||||
Your `audio` element should have a closing tag.
|
||||
|
||||
```js
|
||||
assert.match(code,/<audio.*>[\s\S]*<\/audio>/g);
|
||||
assert.lengthOf(code.match(/<\/audio>/g),1);
|
||||
```
|
||||
|
||||
The `audio` tag should have the `controls` attribute.
|
||||
|
||||
```js
|
||||
assert.exists(document.querySelector('audio')?.getAttribute('controls'));
|
||||
```
|
||||
|
||||
Your code should have one `source` tag.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('source'), 1);
|
||||
```
|
||||
|
||||
Your `source` tag should be inside the `audio` tags.
|
||||
|
||||
```js
|
||||
const audio = document.querySelector('audio');
|
||||
const children = audio.querySelectorAll(`:scope ${'source'}`);
|
||||
assert.lengthOf(children,1);
|
||||
```
|
||||
|
||||
The value for the `src` attribute on the `source` tag should match the link in the instructions exactly.
|
||||
|
||||
```js
|
||||
assert.equal(
|
||||
document.querySelector('source')?.getAttribute('src'),
|
||||
'https://cdn.freecodecamp.org/curriculum/applied-accessibility/screen-reader.mp3'
|
||||
);
|
||||
```
|
||||
|
||||
Your code should include a `type` attribute on the `source` tag with a value of audio/mpeg.
|
||||
|
||||
```js
|
||||
assert.equal(document.querySelector('source')?.getAttribute('type'), 'audio/mpeg');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Real Coding Ninjas</h1>
|
||||
</header>
|
||||
<main>
|
||||
<p>A sound clip of Zersiax's screen reader in action.</p>
|
||||
|
||||
|
||||
|
||||
</main>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Real Coding Ninjas</h1>
|
||||
</header>
|
||||
<main>
|
||||
<p>A sound clip of Zersiax's screen reader in action.</p>
|
||||
<audio controls>
|
||||
<source src="https://cdn.freecodecamp.org/curriculum/applied-accessibility/screen-reader.mp3" type="audio/mpeg"/>
|
||||
</audio>
|
||||
</main>
|
||||
</body>
|
||||
```
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
---
|
||||
id: 587d778a367417b2b2512aa5
|
||||
title: Improve Chart Accessibility with the figure Element
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cGJMqtE'
|
||||
forumTopicId: 301015
|
||||
dashedName: improve-chart-accessibility-with-the-figure-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
HTML5 introduced the `figure` element and the related `figcaption`. Used together, these items wrap a visual representation (like an image, diagram, or chart) along with its caption. Wrapping these elements together gives a two-fold accessibility boost by semantically grouping related content and providing a text alternative explaining the `figure`.
|
||||
|
||||
For data visualizations like charts, the caption can be used to briefly note the trends or conclusions for users with visual impairments. Another challenge covers how to move a table version of the chart's data off-screen (using CSS) for screen reader users.
|
||||
|
||||
Here's an example - note that the `figcaption` goes inside the `figure` tags and can be combined with other elements:
|
||||
|
||||
```html
|
||||
<figure>
|
||||
<img src="roundhouseDestruction.jpeg" alt="Photo of Camper Cat executing a roundhouse kick">
|
||||
<br>
|
||||
<figcaption>
|
||||
Master Camper Cat demonstrates proper form of a roundhouse kick.
|
||||
</figcaption>
|
||||
</figure>
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat is hard at work creating a stacked bar chart showing the amount of time per week to spend training in stealth, combat, and weapons. Help him structure his page better by changing the `div` tag he used to a `figure` tag, and the `p` tag that surrounds the caption to a `figcaption` tag.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should have one `figure` tag.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('figure') , 1);
|
||||
```
|
||||
|
||||
Your code should have one `figcaption` tag.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('figcaption') , 1);
|
||||
```
|
||||
|
||||
Your code should not have any `div` tags.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('div'), 0);
|
||||
```
|
||||
|
||||
Your code should not have any `p` tags.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('p') , 0);
|
||||
```
|
||||
|
||||
The `figcaption` should be a child of the `figure` tag.
|
||||
|
||||
```js
|
||||
const figure = document.querySelector('figure');
|
||||
const children = figure?.querySelectorAll(`:scope ${'figcaption'}`);
|
||||
assert.lengthOf(children, 1);
|
||||
```
|
||||
|
||||
Your `figure` element should have a closing tag.
|
||||
|
||||
```js
|
||||
assert.isTrue(
|
||||
code.match(/<\/figure>/g)?.length === code.match(/<figure>/g)?.length
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Training</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#stealth">Stealth & Agility</a></li>
|
||||
<li><a href="#combat">Combat</a></li>
|
||||
<li><a href="#weapons">Weapons</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<section>
|
||||
|
||||
<!-- Only change code below this line -->
|
||||
<div>
|
||||
<!-- Stacked bar chart will go here -->
|
||||
<br>
|
||||
<p>Breakdown per week of time to spend training in stealth, combat, and weapons.</p>
|
||||
</div>
|
||||
<!-- Only change code above this line -->
|
||||
|
||||
</section>
|
||||
<section id="stealth">
|
||||
<h2>Stealth & Agility Training</h2>
|
||||
<article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
|
||||
<article><h3>No training is NP-complete without parkour</h3></article>
|
||||
</section>
|
||||
<section id="combat">
|
||||
<h2>Combat Training</h2>
|
||||
<article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
|
||||
<article><h3>Goodbye world: 5 proven ways to knock out an opponent</h3></article>
|
||||
</section>
|
||||
<section id="weapons">
|
||||
<h2>Weapons Training</h2>
|
||||
<article><h3>Swords: the best tool to literally divide and conquer</h3></article>
|
||||
<article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
|
||||
</section>
|
||||
</main>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Training</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#stealth">Stealth & Agility</a></li>
|
||||
<li><a href="#combat">Combat</a></li>
|
||||
<li><a href="#weapons">Weapons</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<section>
|
||||
<figure>
|
||||
<!-- Stacked bar chart will go here -->
|
||||
<br>
|
||||
<figcaption>Breakdown per week of time to spend training in stealth, combat, and weapons.</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
<section id="stealth">
|
||||
<h2>Stealth & Agility Training</h2>
|
||||
<article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
|
||||
<article><h3>No training is NP-complete without parkour</h3></article>
|
||||
</section>
|
||||
<section id="combat">
|
||||
<h2>Combat Training</h2>
|
||||
<article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
|
||||
<article><h3>Goodbye world: 5 proven ways to knock out an opponent</h3></article>
|
||||
</section>
|
||||
<section id="weapons">
|
||||
<h2>Weapons Training</h2>
|
||||
<article><h3>Swords: the best tool to literally divide and conquer</h3></article>
|
||||
<article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
|
||||
</section>
|
||||
</main>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
---
|
||||
id: 587d778a367417b2b2512aa6
|
||||
title: Improve Form Field Accessibility with the label Element
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cGJMMAN'
|
||||
forumTopicId: 301016
|
||||
dashedName: improve-form-field-accessibility-with-the-label-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Improving accessibility with semantic HTML markup applies to using both appropriate tag names and attributes. The next several challenges cover some important scenarios using attributes in forms.
|
||||
|
||||
The `label` tag wraps the text for a specific form control item, usually the name or label for a choice. This ties meaning to the item and makes the form more readable. The `for` attribute on a `label` tag explicitly associates that `label` with the form control and is used by screen readers.
|
||||
|
||||
You learned about radio buttons and their labels in a lesson in the Basic HTML section. In that lesson, we wrapped the radio button input element inside a `label` element along with the label text in order to make the text clickable. Another way to achieve this is by using the `for` attribute, as explained in this lesson.
|
||||
|
||||
The value of the `for` attribute must be the same as the value of the `id` attribute of the form control. Here's an example:
|
||||
|
||||
```html
|
||||
<form>
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name">
|
||||
</form>
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat expects a lot of interest in his thoughtful blog posts and wants to include an email sign up form. Add a `for` attribute on the email `label` that matches the `id` on its `input` field.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should have a `for` attribute on the `label` tag that is not empty.
|
||||
|
||||
```js
|
||||
assert.isNotEmpty(document.querySelector('label')?.getAttribute('for'));
|
||||
```
|
||||
|
||||
Your `for` attribute value should match the `id` value on the email `input`.
|
||||
|
||||
```js
|
||||
assert.equal(document.querySelector('label')?.getAttribute('for'), 'email');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<section>
|
||||
<form>
|
||||
<p>Sign up to receive Camper Cat's blog posts by email here!</p>
|
||||
|
||||
|
||||
<label>Email:</label>
|
||||
<input type="text" id="email" name="email">
|
||||
|
||||
|
||||
<input type="submit" name="submit" value="Submit">
|
||||
</form>
|
||||
</section>
|
||||
<article>
|
||||
<h2>The Garfield Files: Lasagna as Training Fuel?</h2>
|
||||
<p>The internet is littered with varying opinions on nutritional paradigms, from catnip paleo to hairball cleanses. But let's turn our attention to an often overlooked fitness fuel, and examine the protein-carb-NOM trifecta that is lasagna...</p>
|
||||
</article>
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
<article>
|
||||
<h2>Defeating your Foe: the Red Dot is Ours!</h2>
|
||||
<p>Felines the world over have been waging war on the most persistent of foes. This red nemesis combines both cunning stealth and lightning speed. But chin up, fellow fighters, our time for victory may soon be near...</p>
|
||||
</article>
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
<article>
|
||||
<h2>Is Chuck Norris a Cat Person?</h2>
|
||||
<p>Chuck Norris is widely regarded as the premier martial artist on the planet, and it's a complete coincidence that anyone who disagrees with this fact mysteriously disappears soon after. But the real question is, is he a cat person?...</p>
|
||||
</article>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<section>
|
||||
<form>
|
||||
<p>Sign up to receive Camper Cat's blog posts by email here!</p>
|
||||
|
||||
|
||||
<label for="email">Email:</label>
|
||||
<input type="text" id="email" name="email">
|
||||
|
||||
|
||||
<input type="submit" name="submit" value="Submit">
|
||||
</form>
|
||||
</section>
|
||||
<article>
|
||||
<h2>The Garfield Files: Lasagna as Training Fuel?</h2>
|
||||
<p>The internet is littered with varying opinions on nutritional paradigms, from catnip paleo to hairball cleanses. But let's turn our attention to an often overlooked fitness fuel, and examine the protein-carb-NOM trifecta that is lasagna...</p>
|
||||
</article>
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
<article>
|
||||
<h2>Defeating your Foe: the Red Dot is Ours!</h2>
|
||||
<p>Felines the world over have been waging war on the most persistent of foes. This red nemesis combines both cunning stealth and lightning speed. But chin up, fellow fighters, our time for victory may soon be near...</p>
|
||||
</article>
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
<article>
|
||||
<h2>Is Chuck Norris a Cat Person?</h2>
|
||||
<p>Chuck Norris is widely regarded as the premier martial artist on the planet, and it's a complete coincidence that anyone who disagrees with this fact mysteriously disappears soon after. But the real question is, is he a cat person?...</p>
|
||||
</article>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
---
|
||||
id: 587d778b367417b2b2512aa7
|
||||
title: Wrap Radio Buttons in a fieldset Element for Better Accessibility
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cVJVefw'
|
||||
forumTopicId: 301030
|
||||
dashedName: wrap-radio-buttons-in-a-fieldset-element-for-better-accessibility
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The next form topic covers the accessibility of radio buttons. Each choice is given a `label` with a `for` attribute tying to the `id` of the corresponding item as covered in the last challenge. Since radio buttons often come in a group where the user must choose one, there's a way to semantically show the choices are part of a set.
|
||||
|
||||
The `fieldset` tag surrounds the entire grouping of radio buttons to achieve this. It often uses a `legend` tag to provide a description for the grouping, which screen readers read for each choice in the `fieldset` element.
|
||||
|
||||
The `fieldset` wrapper and `legend` tag are not necessary when the choices are self-explanatory, like a gender selection. Using a `label` with the `for` attribute for each radio button is sufficient.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```html
|
||||
<form>
|
||||
<fieldset>
|
||||
<legend>Choose one of these three items:</legend>
|
||||
<input id="one" type="radio" name="items" value="one">
|
||||
<label for="one">Choice One</label><br>
|
||||
<input id="two" type="radio" name="items" value="two">
|
||||
<label for="two">Choice Two</label><br>
|
||||
<input id="three" type="radio" name="items" value="three">
|
||||
<label for="three">Choice Three</label>
|
||||
</fieldset>
|
||||
</form>
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat wants information about the ninja level of his users when they sign up for his email list. He's added a set of radio buttons and learned from our last lesson to use `label` tags with `for` attributes for each choice. Go Camper Cat! However, his code still needs some help. Change the `div` tag surrounding the radio buttons to a `fieldset` tag, and change the `p` tag inside it to a `legend`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should have a `fieldset` tag around the radio button set.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('fieldset'),1);
|
||||
```
|
||||
|
||||
The `fieldset` element should have a closing tag.
|
||||
|
||||
```js
|
||||
assert.isTrue(code.match(/<\/fieldset>/g)?.length === code.match(/<fieldset>/g)?.length);
|
||||
```
|
||||
|
||||
Your code should have a `legend` tag around the text asking what level ninja a user is.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('legend') ,1);
|
||||
```
|
||||
|
||||
Your code should not have any `div` tags.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('div') ,0);
|
||||
```
|
||||
|
||||
Your code should no longer have a `p` tag around the text asking what level ninja a user is.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('p') ,4);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<section>
|
||||
<form>
|
||||
<p>Sign up to receive Camper Cat's blog posts by email here!</p>
|
||||
<label for="email">Email:</label>
|
||||
<input type="text" id="email" name="email">
|
||||
|
||||
|
||||
<!-- Only change code below this line -->
|
||||
<div>
|
||||
<p>What level ninja are you?</p>
|
||||
<input id="newbie" type="radio" name="levels" value="newbie">
|
||||
<label for="newbie">Newbie Kitten</label><br>
|
||||
<input id="intermediate" type="radio" name="levels" value="intermediate">
|
||||
<label for="intermediate">Developing Student</label><br>
|
||||
<input id="master" type="radio" name="levels" value="master">
|
||||
<label for="master">Master</label>
|
||||
</div>
|
||||
<!-- Only change code above this line -->
|
||||
|
||||
|
||||
<input type="submit" name="submit" value="Submit">
|
||||
</form>
|
||||
</section>
|
||||
<article>
|
||||
<h2>The Garfield Files: Lasagna as Training Fuel?</h2>
|
||||
<p>The internet is littered with varying opinions on nutritional paradigms, from catnip paleo to hairball cleanses. But let's turn our attention to an often overlooked fitness fuel, and examine the protein-carb-NOM trifecta that is lasagna...</p>
|
||||
</article>
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
<article>
|
||||
<h2>Defeating your Foe: the Red Dot is Ours!</h2>
|
||||
<p>Felines the world over have been waging war on the most persistent of foes. This red nemesis combines both cunning stealth and lightning speed. But chin up, fellow fighters, our time for victory may soon be near...</p>
|
||||
</article>
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
<article>
|
||||
<h2>Is Chuck Norris a Cat Person?</h2>
|
||||
<p>Chuck Norris is widely regarded as the premier martial artist on the planet, and it's a complete coincidence that anyone who disagrees with this fact mysteriously disappears soon after. But the real question is, is he a cat person?...</p>
|
||||
</article>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<section>
|
||||
<form>
|
||||
<p>Sign up to receive Camper Cat's blog posts by email here!</p>
|
||||
<label for="email">Email:</label>
|
||||
<input type="text" id="email" name="email">
|
||||
|
||||
<fieldset>
|
||||
<legend>What level ninja are you?</legend>
|
||||
<input id="newbie" type="radio" name="levels" value="newbie">
|
||||
<label for="newbie">Newbie Kitten</label><br>
|
||||
<input id="intermediate" type="radio" name="levels" value="intermediate">
|
||||
<label for="intermediate">Developing Student</label><br>
|
||||
<input id="master" type="radio" name="levels" value="master">
|
||||
<label for="master">Master</label>
|
||||
</fieldset>
|
||||
|
||||
<input type="submit" name="submit" value="Submit">
|
||||
</form>
|
||||
</section>
|
||||
<article>
|
||||
<h2>The Garfield Files: Lasagna as Training Fuel?</h2>
|
||||
<p>The internet is littered with varying opinions on nutritional paradigms, from catnip paleo to hairball cleanses. But let's turn our attention to an often overlooked fitness fuel, and examine the protein-carb-NOM trifecta that is lasagna...</p>
|
||||
</article>
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
<article>
|
||||
<h2>Defeating your Foe: the Red Dot is Ours!</h2>
|
||||
<p>Felines the world over have been waging war on the most persistent of foes. This red nemesis combines both cunning stealth and lightning speed. But chin up, fellow fighters, our time for victory may soon be near...</p>
|
||||
</article>
|
||||
<img src="samuraiSwords.jpeg" alt="">
|
||||
<article>
|
||||
<h2>Is Chuck Norris a Cat Person?</h2>
|
||||
<p>Chuck Norris is widely regarded as the premier martial artist on the planet, and it's a complete coincidence that anyone who disagrees with this fact mysteriously disappears soon after. But the real question is, is he a cat person?...</p>
|
||||
</article>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
---
|
||||
id: 587d778b367417b2b2512aa8
|
||||
title: Add an Accessible Date Picker
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cR3bRbCV'
|
||||
forumTopicId: 301008
|
||||
dashedName: add-an-accessible-date-picker
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Forms often include the `input` field, which can be used to create several different form controls. The `type` attribute on this element indicates what kind of `input` element will be created.
|
||||
|
||||
You may have noticed the `text` and `submit` input types in prior challenges, and HTML5 introduced an option to specify a `date` field. Depending on browser support, a date picker shows up in the `input` field when it's in focus, which makes filling in a form easier for all users.
|
||||
|
||||
For older browsers, the type will default to `text`, so it helps to show users the expected date format in the `label` or `placeholder` text just in case.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```html
|
||||
<label for="input1">Enter a date:</label>
|
||||
<input type="date" id="input1" name="input1">
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat is setting up a Mortal Kombat tournament and wants to ask his competitors to see what date works best. Add an `input` tag with a `type` attribute of `date`, an `id` attribute of `pickdate`, and a `name` attribute of `date`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add one `input` tag for the date selector field.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('input'), 2);
|
||||
```
|
||||
|
||||
Your `input` tag should have a `type` attribute with a value of `date`.
|
||||
|
||||
```js
|
||||
assert.equal(document.querySelector('input')?.getAttribute('type'), 'date');
|
||||
```
|
||||
|
||||
Your `input` tag should have an `id` attribute with a value of `pickdate`.
|
||||
|
||||
```js
|
||||
assert.equal(document.querySelector('input')?.getAttribute('id'),'pickdate');
|
||||
```
|
||||
|
||||
Your `input` tag should have a `name` attribute with a value of `date`.
|
||||
|
||||
```js
|
||||
assert.equal(document.querySelector('input')?.getAttribute('name'), 'date');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Tournaments</h1>
|
||||
</header>
|
||||
<main>
|
||||
<section>
|
||||
<h2>Mortal Kombat Tournament Survey</h2>
|
||||
<form>
|
||||
<p>Tell us the best date for the competition</p>
|
||||
<label for="pickdate">Preferred Date:</label>
|
||||
|
||||
<!-- Only change code below this line -->
|
||||
|
||||
|
||||
|
||||
<!-- Only change code above this line -->
|
||||
|
||||
<input type="submit" name="submit" value="Submit">
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Tournaments</h1>
|
||||
</header>
|
||||
<main>
|
||||
<section>
|
||||
<h2>Mortal Kombat Tournament Survey</h2>
|
||||
<form>
|
||||
<p>Tell us the best date for the competition</p>
|
||||
<label for="pickdate">Preferred Date:</label>
|
||||
<input type="date" id="pickdate" name="date">
|
||||
<input type="submit" name="submit" value="Submit">
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
---
|
||||
id: 587d778c367417b2b2512aa9
|
||||
title: Standardize Times with the HTML5 datetime Attribute
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cmzMgtz'
|
||||
forumTopicId: 301025
|
||||
dashedName: standardize-times-with-the-html5-datetime-attribute
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Continuing with the date theme, HTML5 also introduced the `time` element along with a `datetime` attribute to standardize times. The `time` element is an inline element that can wrap a date or time on a page. A `datetime` attribute holds a valid format of that date. This is the value accessed by assistive devices. It helps avoid confusion by stating a standardized version of a time, even if it's informally or colloquially written in the text.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```html
|
||||
<p>Master Camper Cat officiated the cage match between Goro and Scorpion <time datetime="2013-02-13">last Wednesday</time>, which ended in a draw.</p>
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat's Mortal Kombat survey results are in! Wrap a `time` tag around the text `Thursday, September 15<sup>th</sup>` and add a `datetime` attribute to it set to `2016-09-15`.
|
||||
|
||||
# --before-all--
|
||||
|
||||
```js
|
||||
const getTimeElement = () => {
|
||||
const pElement = [...document.querySelectorAll("article > p")]
|
||||
.filter(x => x?.textContent?.includes("Thank you to everyone for responding to Master Camper Cat's survey."));
|
||||
|
||||
return pElement[0] ? pElement[0].querySelector("time") : null;
|
||||
};
|
||||
|
||||
const getDatetimeAttr = () => {
|
||||
const timeElement = getTimeElement();
|
||||
return timeElement?.getAttribute("datetime");
|
||||
};
|
||||
```
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should have a `p` element which includes the text `Thank you to everyone for responding to Master Camper Cat's survey.` and include a `time` element.
|
||||
|
||||
```js
|
||||
assert.exists(getTimeElement());
|
||||
```
|
||||
|
||||
Your added `time` tags should wrap around the text `Thursday, September 15<sup>th</sup>`.
|
||||
|
||||
```js
|
||||
assert.strictEqual(getTimeElement()?.innerHTML?.trim(), 'Thursday, September 15<sup>th</sup>');
|
||||
```
|
||||
|
||||
Your added `time` tag should have a `datetime` attribute that is not empty.
|
||||
|
||||
```js
|
||||
assert(getDatetimeAttr()?.length != 0);
|
||||
```
|
||||
|
||||
Your added `datetime` attribute should be set to a value of `2016-09-15`.
|
||||
|
||||
```js
|
||||
assert.equal(getDatetimeAttr() , '2016-09-15');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Tournaments</h1>
|
||||
</header>
|
||||
<article>
|
||||
<h2>Mortal Kombat Tournament Survey Results</h2>
|
||||
|
||||
<!-- Only change code below this line -->
|
||||
|
||||
<p>Thank you to everyone for responding to Master Camper Cat's survey. The best day to host the vaunted Mortal Kombat tournament is Thursday, September 15<sup>th</sup>. May the best ninja win!</p>
|
||||
|
||||
<!-- Only change code above this line -->
|
||||
|
||||
<section>
|
||||
<h3>Comments:</h3>
|
||||
<article>
|
||||
<p>Posted by: Sub-Zero on <time datetime="2016-08-13T20:01Z">August 13<sup>th</sup></time></p>
|
||||
<p>Johnny Cage better be there, I'll finish him!</p>
|
||||
</article>
|
||||
<article>
|
||||
<p>Posted by: Doge on <time datetime="2016-08-15T08:12Z">August 15<sup>th</sup></time></p>
|
||||
<p>Wow, much combat, so mortal.</p>
|
||||
</article>
|
||||
<article>
|
||||
<p>Posted by: The Grim Reaper on <time datetime="2016-08-16T00:00Z">August 16<sup>th</sup></time></p>
|
||||
<p>Looks like I'll be busy that day.</p>
|
||||
</article>
|
||||
</section>
|
||||
</article>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Tournaments</h1>
|
||||
</header>
|
||||
<article>
|
||||
<h2>Mortal Kombat Tournament Survey Results</h2>
|
||||
|
||||
<p>Thank you to everyone for responding to Master Camper Cat's survey. The best day to host the vaunted Mortal Kombat tournament is <time datetime="2016-09-15">Thursday, September 15<sup>th</sup></time>. May the best ninja win!</p>
|
||||
|
||||
<section>
|
||||
<h3>Comments:</h3>
|
||||
<article>
|
||||
<p>Posted by: Sub-Zero on <time datetime="2016-08-13T20:01Z">August 13<sup>th</sup></time></p>
|
||||
<p>Johnny Cage better be there, I'll finish him!</p>
|
||||
</article>
|
||||
<article>
|
||||
<p>Posted by: Doge on <time datetime="2016-08-15T08:12Z">August 15<sup>th</sup></time></p>
|
||||
<p>Wow, much combat, so mortal.</p>
|
||||
</article>
|
||||
<article>
|
||||
<p>Posted by: The Grim Reaper on <time datetime="2016-08-16T00:00Z">August 16<sup>th</sup></time></p>
|
||||
<p>Looks like I'll be busy that day.</p>
|
||||
</article>
|
||||
</section>
|
||||
</article>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
---
|
||||
id: 587d778d367417b2b2512aaa
|
||||
title: Make Elements Only Visible to a Screen Reader by Using Custom CSS
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cJ8QGkhJ'
|
||||
forumTopicId: 301020
|
||||
dashedName: make-elements-only-visible-to-a-screen-reader-by-using-custom-css
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Have you noticed that all of the applied accessibility challenges so far haven't used any CSS? This shows the importance of using a logical document outline and semantically meaningful tags around your content before introducing the visual design aspect.
|
||||
|
||||
However, CSS's magic can also improve accessibility on your page when you want to visually hide content meant only for screen readers. This happens when information is in a visual format (like a chart), but screen reader users need an alternative presentation (like a table) to access the data. CSS is used to position the screen reader-only elements off the visual area of the browser window.
|
||||
|
||||
Here's an example of the CSS rules that accomplish this:
|
||||
|
||||
```css
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
left: -10000px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
top: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** The following CSS approaches will NOT do the same thing:
|
||||
|
||||
<ul>
|
||||
<li><code>display: none;</code> or <code>visibility: hidden;</code> hides content for everyone, including screen reader users</li>
|
||||
<li>Zero values for pixel sizes, such as <code>width: 0px; height: 0px;</code> removes that element from the flow of your document, meaning screen readers will ignore it</li>
|
||||
</ul>
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat created a really cool stacked bar chart for his training page, and put the data into a table for his visually impaired users. The table already has an `sr-only` class, but the CSS rules aren't filled in yet. Give the `position` an `absolute` value, the `left` a `-10000px` value, and the `width` and `height` both `1px` values.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should set the `position` property of the `sr-only` class to a value of `absolute`.
|
||||
|
||||
```js
|
||||
const srOnly = document.querySelector('.sr-only');
|
||||
const position = window.getComputedStyle(srOnly).position;
|
||||
assert.equal(position, 'absolute');
|
||||
```
|
||||
|
||||
Your code should set the `left` property of the `sr-only` class to a value of `-10000px`.
|
||||
|
||||
```js
|
||||
const srOnly = document.querySelector('.sr-only');
|
||||
const left = window.getComputedStyle(srOnly).left;
|
||||
assert.equal(left, '-10000px');
|
||||
```
|
||||
|
||||
Your code should set the `width` property of the `sr-only` class to a value of `1` pixel.
|
||||
|
||||
```js
|
||||
assert.match(code , /width:\s*?1px/gi);
|
||||
```
|
||||
|
||||
Your code should set the `height` property of the `sr-only` class to a value of `1` pixel.
|
||||
|
||||
```js
|
||||
assert.match(code , /height:\s*?1px/gi);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<head>
|
||||
<style>
|
||||
.sr-only {
|
||||
position: ;
|
||||
left: ;
|
||||
width: ;
|
||||
height: ;
|
||||
top: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Training</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#stealth">Stealth & Agility</a></li>
|
||||
<li><a href="#combat">Combat</a></li>
|
||||
<li><a href="#weapons">Weapons</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<section>
|
||||
<h2>Master Camper Cat's Beginner Three Week Training Program</h2>
|
||||
<figure>
|
||||
<!-- Stacked bar chart of weekly training -->
|
||||
<p>[Stacked bar chart]</p>
|
||||
<br />
|
||||
<figcaption>Breakdown per week of time to spend training in stealth, combat, and weapons.</figcaption>
|
||||
</figure>
|
||||
<table class="sr-only">
|
||||
<caption>Hours of Weekly Training in Stealth, Combat, and Weapons</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th scope="col">Stealth & Agility</th>
|
||||
<th scope="col">Combat</th>
|
||||
<th scope="col">Weapons</th>
|
||||
<th scope="col">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Week One</th>
|
||||
<td>3</td>
|
||||
<td>5</td>
|
||||
<td>2</td>
|
||||
<td>10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Week Two</th>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
<td>3</td>
|
||||
<td>12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Week Three</th>
|
||||
<td>4</td>
|
||||
<td>6</td>
|
||||
<td>3</td>
|
||||
<td>13</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section id="stealth">
|
||||
<h2>Stealth & Agility Training</h2>
|
||||
<article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
|
||||
<article><h3>No training is NP-complete without parkour</h3></article>
|
||||
</section>
|
||||
<section id="combat">
|
||||
<h2>Combat Training</h2>
|
||||
<article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
|
||||
<article><h3>Goodbye, world: 5 proven ways to knock out an opponent</h3></article>
|
||||
</section>
|
||||
<section id="weapons">
|
||||
<h2>Weapons Training</h2>
|
||||
<article><h3>Swords: the best tool to literally divide and conquer</h3></article>
|
||||
<article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
|
||||
</section>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<head>
|
||||
<style>
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
left: -10000px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
top: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Training</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#stealth">Stealth & Agility</a></li>
|
||||
<li><a href="#combat">Combat</a></li>
|
||||
<li><a href="#weapons">Weapons</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<section>
|
||||
<h2>Master Camper Cat's Beginner Three Week Training Program</h2>
|
||||
<figure>
|
||||
<!-- Stacked bar chart of weekly training -->
|
||||
<p>[Stacked bar chart]</p>
|
||||
<br />
|
||||
<figcaption>Breakdown per week of time to spend training in stealth, combat, and weapons.</figcaption>
|
||||
</figure>
|
||||
<table class="sr-only">
|
||||
<caption>Hours of Weekly Training in Stealth, Combat, and Weapons</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th scope="col">Stealth & Agility</th>
|
||||
<th scope="col">Combat</th>
|
||||
<th scope="col">Weapons</th>
|
||||
<th scope="col">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Week One</th>
|
||||
<td>3</td>
|
||||
<td>5</td>
|
||||
<td>2</td>
|
||||
<td>10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Week Two</th>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
<td>3</td>
|
||||
<td>12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Week Three</th>
|
||||
<td>4</td>
|
||||
<td>6</td>
|
||||
<td>3</td>
|
||||
<td>13</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section id="stealth">
|
||||
<h2>Stealth & Agility Training</h2>
|
||||
<article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
|
||||
<article><h3>No training is NP-complete without parkour</h3></article>
|
||||
</section>
|
||||
<section id="combat">
|
||||
<h2>Combat Training</h2>
|
||||
<article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
|
||||
<article><h3>Goodbye, world: 5 proven ways to knock out an opponent</h3></article>
|
||||
</section>
|
||||
<section id="weapons">
|
||||
<h2>Weapons Training</h2>
|
||||
<article><h3>Swords: the best tool to literally divide and conquer</h3></article>
|
||||
<article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
|
||||
</section>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
---
|
||||
id: 587d778e367417b2b2512aab
|
||||
title: Improve Readability with High Contrast Text
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cKb3nCq'
|
||||
forumTopicId: 301017
|
||||
dashedName: improve-readability-with-high-contrast-text
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Low contrast between the foreground and background colors can make text difficult to read. Sufficient contrast improves your content's readability, but what exactly does "sufficient" mean?
|
||||
|
||||
The Web Content Accessibility Guidelines (WCAG) recommend at least a 4.5 to 1 contrast ratio for normal text. The ratio is calculated by comparing the relative luminance values of two colors. This ranges from 1:1 for the same color, or no contrast, to 21:1 for white against black, the most substantial contrast. There are many contrast checking tools available online that calculate this ratio for you.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat's choice of light gray text on a white background for his recent blog post has a 1.5:1 contrast ratio, making it hard to read. Change the `color` of the text from the current gray (`#D3D3D3`) to a darker gray (`#636363`) to improve the contrast ratio to 6:1.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should change the text `color` for the `body` to the darker gray.
|
||||
|
||||
```js
|
||||
const body = document.querySelector('body');
|
||||
const bodyColor = window.getComputedStyle(body).color;
|
||||
assert(bodyColor == 'rgb(99, 99, 99)');
|
||||
```
|
||||
|
||||
Your code should not change the `background-color` for the `body`.
|
||||
|
||||
```js
|
||||
const body = document.querySelector('body');
|
||||
const backgroundColor = window.getComputedStyle(body).backgroundColor;
|
||||
assert.equal(backgroundColor , 'rgb(255, 255, 255)');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
color: #D3D3D3;
|
||||
background-color: #FFF;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<article>
|
||||
<h2>A Word on the Recent Catnip Doping Scandal</h2>
|
||||
<p>The influence that catnip has on feline behavior is well-documented, and its use as an herbal supplement in competitive ninja circles remains controversial. Once again, the debate to ban the substance is brought to the public's attention after the high-profile win of Kittytron, a long-time proponent and user of the green stuff, at the Claw of Fury tournament.</p>
|
||||
<p>As I've stated in the past, I firmly believe a true ninja's skills must come from within, with no external influences. My own catnip use shall continue as purely recreational.</p>
|
||||
</article>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
color: #636363;
|
||||
background-color: #FFF;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<article>
|
||||
<h2>A Word on the Recent Catnip Doping Scandal</h2>
|
||||
<p>The influence that catnip has on feline behavior is well-documented, and its use as an herbal supplement in competitive ninja circles remains controversial. Once again, the debate to ban the substance is brought to the public's attention after the high-profile win of Kittytron, a long-time proponent and user of the green stuff, at the Claw of Fury tournament.</p>
|
||||
<p>As I've stated in the past, I firmly believe a true ninja's skills must come from within, with no external influences. My own catnip use shall continue as purely recreational.</p>
|
||||
</article>
|
||||
</body>
|
||||
```
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
---
|
||||
id: 587d778f367417b2b2512aac
|
||||
title: Avoid Colorblindness Issues by Using Sufficient Contrast
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cmzMEUw'
|
||||
forumTopicId: 301012
|
||||
dashedName: avoid-colorblindness-issues-by-using-sufficient-contrast
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Color is a large part of visual design, but its use introduces two accessibility issues. First, color alone should not be used as the only way to convey important information because screen reader users won't see it. Second, foreground and background colors need sufficient contrast so colorblind users can distinguish them.
|
||||
|
||||
Previous challenges covered having text alternatives to address the first issue. The last challenge introduced contrast checking tools to help with the second. The WCAG-recommended contrast ratio of 4.5:1 applies for color use as well as gray-scale combinations.
|
||||
|
||||
Colorblind users have trouble distinguishing some colors from others - usually in hue but sometimes lightness as well. You may recall the contrast ratio is calculated using the relative luminance (or lightness) values of the foreground and background colors.
|
||||
|
||||
In practice, the 4.5:1 contrast ratio can be reached by shading (adding black to) the darker color and tinting (adding white to) the lighter color. Darker shades on the color wheel are considered to be shades of blues, violets, magentas, and reds, whereas lighter tinted colors are oranges, yellows, greens, and blue-greens.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat is experimenting with using color for his blog text and background, but his current combination of a greenish `background-color` with maroon text `color` has a 2.5:1 contrast ratio. You can easily adjust the lightness of the colors since he declared them using the CSS `hsl()` property (which stands for hue, saturation, lightness) by changing the third argument. Increase the `background-color` lightness value from 35% to 55%, and decrease the `color` lightness value from 20% to 15%. This improves the contrast to 5.9:1.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should only change the lightness value for the text `color` property to a value of 15%.
|
||||
|
||||
```js
|
||||
assert.match(code ,/color:\s*?hsl\(0,\s*?55%,\s*?15%\)/gi);
|
||||
```
|
||||
|
||||
Your code should only change the lightness value for the `background-color` property to a value of 55%.
|
||||
|
||||
```js
|
||||
assert.match(code ,/background-color:\s*?hsl\(120,\s*?25%,\s*?55%\)/gi);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
color: hsl(0, 55%, 20%);
|
||||
background-color: hsl(120, 25%, 35%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<article>
|
||||
<h2>A Word on the Recent Catnip Doping Scandal</h2>
|
||||
<p>The influence that catnip has on feline behavior is well-documented, and its use as an herbal supplement in competitive ninja circles remains controversial. Once again, the debate to ban the substance is brought to the public's attention after the high-profile win of Kittytron, a long-time proponent and user of the green stuff, at the Claw of Fury tournament.</p>
|
||||
<p>As I've stated in the past, I firmly believe a true ninja's skills must come from within, with no external influences. My own catnip use shall continue as purely recreational.</p>
|
||||
</article>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
color: hsl(0, 55%, 15%);
|
||||
background-color: hsl(120, 25%, 55%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<article>
|
||||
<h2>A Word on the Recent Catnip Doping Scandal</h2>
|
||||
<p>The influence that catnip has on feline behavior is well-documented, and its use as an herbal supplement in competitive ninja circles remains controversial. Once again, the debate to ban the substance is brought to the public's attention after the high-profile win of Kittytron, a long-time proponent and user of the green stuff, at the Claw of Fury tournament.</p>
|
||||
<p>As I've stated in the past, I firmly believe a true ninja's skills must come from within, with no external influences. My own catnip use shall continue as purely recreational.</p>
|
||||
</article>
|
||||
</body>
|
||||
```
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
---
|
||||
id: 587d778f367417b2b2512aad
|
||||
title: >-
|
||||
Avoid Colorblindness Issues by Carefully Choosing Colors that Convey
|
||||
Information
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/c437as3'
|
||||
forumTopicId: 301011
|
||||
dashedName: >-
|
||||
avoid-colorblindness-issues-by-carefully-choosing-colors-that-convey-information
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
There are various forms of colorblindness. These can range from a reduced sensitivity to a certain wavelength of light to the inability to see color at all. The most common form is a reduced sensitivity to detect greens.
|
||||
|
||||
For example, if two similar green colors are the foreground and background color of your content, a colorblind user may not be able to distinguish them. Close colors can be thought of as neighbors on the color wheel, and those combinations should be avoided when conveying important information.
|
||||
|
||||
**Note:** Some online color picking tools include visual simulations of how colors appear for different types of colorblindness. These are great resources in addition to online contrast checking calculators.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat is testing different styles for an important button, but the yellow (`#FFFF33`) `background-color` and the green (`#33FF33`) text `color` are neighboring hues on the color wheel and virtually indistinguishable for some colorblind users. (Their similar lightness also fails the contrast ratio check). Change the text `color` to a dark blue (`#003366`) to solve both problems.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should change the text `color` for the `button` to the dark blue.
|
||||
|
||||
```js
|
||||
const button = document.querySelector('button');
|
||||
const buttonColor = window.getComputedStyle(button).color;
|
||||
assert.equal(buttonColor, 'rgb(0, 51, 102)');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<head>
|
||||
<style>
|
||||
button {
|
||||
color: #33FF33;
|
||||
background-color: #FFFF33;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Danger!</h1>
|
||||
</header>
|
||||
<button>Delete Internet</button>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<head>
|
||||
<style>
|
||||
button {
|
||||
color: #003366;
|
||||
background-color: #FFFF33;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Danger!</h1>
|
||||
</header>
|
||||
<button>Delete Internet</button>
|
||||
</body>
|
||||
```
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
---
|
||||
id: 587d778f367417b2b2512aae
|
||||
title: Give Links Meaning by Using Descriptive Link Text
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/c437DcV'
|
||||
forumTopicId: 301013
|
||||
dashedName: give-links-meaning-by-using-descriptive-link-text
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Screen reader users have various options for what type of content their device reads. These options include skipping to (or over) landmark elements, jumping to the main content, or getting a page summary from the headings. Another option is to only hear the links available on a page.
|
||||
|
||||
Screen readers do this by reading the link text, or what's between the anchor (`a`) tags. Having a list of "click here" or "read more" links isn't helpful. Instead, use brief but descriptive text within the `a` tags to provide more meaning for these users.
|
||||
|
||||
# --instructions--
|
||||
|
||||
The link text that Camper Cat is using is not very descriptive without the surrounding context. Move the anchor (`a`) tags so they wrap around the text `information about batteries` instead of `Click here`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should move the anchor `a` tags from around the words `Click here` to wrap around the words `information about batteries`.
|
||||
|
||||
```js
|
||||
assert.match(document.querySelector('a')?.textContent, /^(information about batteries)$/g);
|
||||
```
|
||||
|
||||
The `a` element should have an `href` attribute with a value of an empty string `""`.
|
||||
|
||||
```js
|
||||
assert.isEmpty(document.querySelector('a')?.getAttribute('href'));
|
||||
```
|
||||
|
||||
The `a` element should have a closing tag.
|
||||
|
||||
```js
|
||||
assert.isTrue(code.match(/<\/a>/g)?.length === code.match(/<a href=(''|"")>/g)?.length);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<article>
|
||||
<h2>Defeating your Foe: the Red Dot is Ours!</h2>
|
||||
<p>Felines the world over have been waging war on the most persistent of foes. This red nemesis combines both cunning stealth and lightning speed. But chin up, fellow fighters, our time for victory may soon be near. <a href="">Click here</a> for information about batteries</p>
|
||||
</article>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<article>
|
||||
<h2>Defeating your Foe: the Red Dot is Ours!</h2>
|
||||
<p>Felines the world over have been waging war on the most persistent of foes. This red nemesis combines both cunning stealth and lightning speed. But chin up, fellow fighters, our time for victory may soon be near. Click here for <a href="">information about batteries</a></p>
|
||||
</article>
|
||||
</body>
|
||||
```
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
---
|
||||
id: 587d7790367417b2b2512aaf
|
||||
title: Make Links Navigable with HTML Access Keys
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cQvmaTp'
|
||||
forumTopicId: 301021
|
||||
dashedName: make-links-navigable-with-html-access-keys
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
HTML offers the `accesskey` attribute to specify a shortcut key to activate or bring focus to an element. Adding an `accesskey` attribute can make navigation more efficient for keyboard-only users.
|
||||
|
||||
HTML5 allows this attribute to be used on any element, but it's particularly useful when it's used with interactive ones. This includes links, buttons, and form controls.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```html
|
||||
<button accesskey="b">Important Button</button>
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat wants the links around the two blog article titles to have keyboard shortcuts so his site's users can quickly navigate to the full story. Add an `accesskey` attribute to both links and set the first one to `g` (for Garfield) and the second one to `c` (for Chuck Norris).
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add an `accesskey` attribute to the `a` tag with the `id` of `first`.
|
||||
|
||||
```js
|
||||
assert.isTrue(document.querySelector('#first')?.hasAttribute('accesskey'));
|
||||
```
|
||||
|
||||
Your code should add an `accesskey` attribute to the `a` tag with the `id` of `second`.
|
||||
|
||||
```js
|
||||
assert.isTrue(document.querySelector('#second')?.hasAttribute('accesskey'));
|
||||
```
|
||||
|
||||
Your code should set the `accesskey` attribute on the `a` tag with the `id` of `first` to `g`. Note that case matters.
|
||||
|
||||
```js
|
||||
assert.equal(document.querySelector('#first')?.getAttribute('accesskey'), 'g');
|
||||
```
|
||||
|
||||
Your code should set the `accesskey` attribute on the `a` tag with the `id` of `second` to `c`. Note that case matters.
|
||||
|
||||
```js
|
||||
assert.equal(document.querySelector('#second')?.getAttribute('accesskey'), 'c');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<article>
|
||||
|
||||
|
||||
<h2><a id="first" href="#">The Garfield Files: Lasagna as Training Fuel?</a></h2>
|
||||
|
||||
|
||||
<p>The internet is littered with varying opinions on nutritional paradigms, from catnip paleo to hairball cleanses. But let's turn our attention to an often overlooked fitness fuel, and examine the protein-carb-NOM trifecta that is lasagna...</p>
|
||||
</article>
|
||||
<article>
|
||||
|
||||
|
||||
<h2><a id="second" href="#">Is Chuck Norris a Cat Person?</a></h2>
|
||||
|
||||
|
||||
<p>Chuck Norris is widely regarded as the premier martial artist on the planet, and it's a complete coincidence that anyone who disagrees with this fact mysteriously disappears soon after. But the real question is, is he a cat person?...</p>
|
||||
</article>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deep Thoughts with Master Camper Cat</h1>
|
||||
</header>
|
||||
<article>
|
||||
|
||||
|
||||
<h2><a id="first" accesskey="g" href="#">The Garfield Files: Lasagna as Training Fuel?</a></h2>
|
||||
|
||||
|
||||
<p>The internet is littered with varying opinions on nutritional paradigms, from catnip paleo to hairball cleanses. But let's turn our attention to an often overlooked fitness fuel, and examine the protein-carb-NOM trifecta that is lasagna...</p>
|
||||
</article>
|
||||
<article>
|
||||
|
||||
|
||||
<h2><a id="second" accesskey="c" href="#">Is Chuck Norris a Cat Person?</a></h2>
|
||||
|
||||
|
||||
<p>Chuck Norris is widely regarded as the premier martial artist on the planet, and it's a complete coincidence that anyone who disagrees with this fact mysteriously disappears soon after. But the real question is, is he a cat person?...</p>
|
||||
</article>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
---
|
||||
id: 587d7790367417b2b2512ab0
|
||||
title: Use tabindex to Add Keyboard Focus to an Element
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cmzMDHW'
|
||||
forumTopicId: 301027
|
||||
dashedName: use-tabindex-to-add-keyboard-focus-to-an-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The HTML `tabindex` attribute has three distinct functions relating to an element's keyboard focus. When it's on a tag, it indicates that the element can be focused on. The value (an integer that's positive, negative, or zero) determines the behavior.
|
||||
|
||||
Certain elements, such as links and form controls, automatically receive keyboard focus when a user tabs through a page. It's in the same order as the elements come in the HTML source markup. This same functionality can be given to other elements, such as `div`, `span`, and `p`, by placing a `tabindex="0"` attribute on them. Here's an example:
|
||||
|
||||
```html
|
||||
<div tabindex="0">I need keyboard focus!</div>
|
||||
```
|
||||
|
||||
**Note:** A negative `tabindex` value (typically -1) indicates that an element is focusable, but is not reachable by the keyboard. This method is generally used to bring focus to content programmatically (like when a `div` used for a pop-up window is activated), and is beyond the scope of these challenges.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat created a new survey to collect information about his users. He knows input fields automatically get keyboard focus, but he wants to make sure his keyboard users pause at the instructions while tabbing through the items. Add a `tabindex` attribute to the `p` tag and set its value to `0`. Bonus - using `tabindex` also enables the CSS pseudo-class `:focus` to work on the `p` tag.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add a `tabindex` attribute to the `p` tag that holds the form instructions.
|
||||
|
||||
```js
|
||||
assert.isNotNull(document.querySelector('p')?.getAttribute('tabindex'));
|
||||
```
|
||||
|
||||
Your code should set the `tabindex` attribute on the `p` tag to a value of 0.
|
||||
|
||||
```js
|
||||
assert.equal(document.querySelector('p')?.getAttribute('tabindex'), '0');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<head>
|
||||
<style>
|
||||
p:focus {
|
||||
background-color: yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Ninja Survey</h1>
|
||||
</header>
|
||||
<section>
|
||||
<form>
|
||||
|
||||
|
||||
<p>Instructions: Fill in ALL your information then click <b>Submit</b></p>
|
||||
|
||||
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username"><br>
|
||||
<fieldset>
|
||||
<legend>What level ninja are you?</legend>
|
||||
<input id="newbie" type="radio" name="levels" value="newbie">
|
||||
<label for="newbie">Newbie Kitten</label><br>
|
||||
<input id="intermediate" type="radio" name="levels" value="intermediate">
|
||||
<label for="intermediate">Developing Student</label><br>
|
||||
<input id="master" type="radio" name="levels" value="master">
|
||||
<label for="master">9th Life Master</label>
|
||||
</fieldset>
|
||||
<br>
|
||||
<fieldset>
|
||||
<legend>Select your favorite weapons:</legend>
|
||||
<input id="stars" type="checkbox" name="weapons" value="stars">
|
||||
<label for="stars">Throwing Stars</label><br>
|
||||
<input id="nunchucks" type="checkbox" name="weapons" value="nunchucks">
|
||||
<label for="nunchucks">Nunchucks</label><br>
|
||||
<input id="sai" type="checkbox" name="weapons" value="sai">
|
||||
<label for="sai">Sai Set</label><br>
|
||||
<input id="sword" type="checkbox" name="weapons" value="sword">
|
||||
<label for="sword">Sword</label>
|
||||
</fieldset>
|
||||
<br>
|
||||
<input type="submit" name="submit" value="Submit">
|
||||
</form><br>
|
||||
</section>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<head>
|
||||
<style>
|
||||
p:focus {
|
||||
background-color: yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Ninja Survey</h1>
|
||||
</header>
|
||||
<section>
|
||||
<form>
|
||||
|
||||
|
||||
<p tabindex="0">Instructions: Fill in ALL your information then click <b>Submit</b></p>
|
||||
|
||||
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username"><br>
|
||||
<fieldset>
|
||||
<legend>What level ninja are you?</legend>
|
||||
<input id="newbie" type="radio" name="levels" value="newbie">
|
||||
<label for="newbie">Newbie Kitten</label><br>
|
||||
<input id="intermediate" type="radio" name="levels" value="intermediate">
|
||||
<label for="intermediate">Developing Student</label><br>
|
||||
<input id="master" type="radio" name="levels" value="master">
|
||||
<label for="master">9th Life Master</label>
|
||||
</fieldset>
|
||||
<br>
|
||||
<fieldset>
|
||||
<legend>Select your favorite weapons:</legend>
|
||||
<input id="stars" type="checkbox" name="weapons" value="stars">
|
||||
<label for="stars">Throwing Stars</label><br>
|
||||
<input id="nunchucks" type="checkbox" name="weapons" value="nunchucks">
|
||||
<label for="nunchucks">Nunchucks</label><br>
|
||||
<input id="sai" type="checkbox" name="weapons" value="sai">
|
||||
<label for="sai">Sai Set</label><br>
|
||||
<input id="sword" type="checkbox" name="weapons" value="sword">
|
||||
<label for="sword">Sword</label>
|
||||
</fieldset>
|
||||
<br>
|
||||
<input type="submit" name="submit" value="Submit">
|
||||
</form><br>
|
||||
</section>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
```
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
---
|
||||
id: 587d7790367417b2b2512ab1
|
||||
title: Use tabindex to Specify the Order of Keyboard Focus for Several Elements
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cmzRRcb'
|
||||
forumTopicId: 301028
|
||||
dashedName: use-tabindex-to-specify-the-order-of-keyboard-focus-for-several-elements
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The `tabindex` attribute also specifies the exact tab order of elements. This is achieved when the attribute's value is set to a positive number of 1 or higher.
|
||||
|
||||
Setting a `tabindex="1"` will bring keyboard focus to that element first. Then it cycles through the sequence of specified `tabindex` values (2, 3, etc.), before moving to default and `tabindex="0"` items.
|
||||
|
||||
It's important to note that when the tab order is set this way, it overrides the default order (which uses the HTML source). This may confuse users who are expecting to start navigation from the top of the page. This technique may be necessary in some circumstances, but in terms of accessibility, take care before applying it.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```html
|
||||
<div tabindex="1">I get keyboard focus, and I get it first!</div>
|
||||
```
|
||||
|
||||
```html
|
||||
<div tabindex="2">I get keyboard focus, and I get it second!</div>
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
Camper Cat has a search field on his Inspirational Quotes page that he plans to position in the upper right corner with CSS. He wants the search `input` and submit `input` form controls to be the first two items in the tab order. Add a `tabindex` attribute set to `1` to the `search` `input`, and a `tabindex` attribute set to `2` to the `submit` `input`.
|
||||
|
||||
Another thing to note is that some browsers may place you in the middle of your tab order when an element is clicked. An element has been added to the page that ensures you will always start at the beginning of your tab order.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add a `tabindex` attribute to the `search` `input` tag.
|
||||
|
||||
```js
|
||||
assert.isNotNull(document.querySelector('#search')?.getAttribute('tabindex'));
|
||||
```
|
||||
|
||||
Your code should add a `tabindex` attribute to the `submit` `input` tag.
|
||||
|
||||
```js
|
||||
assert.isNotNull(document.querySelector('#submit')?.getAttribute('tabindex'));
|
||||
```
|
||||
|
||||
Your code should set the `tabindex` attribute on the `search` `input` tag to a value of 1.
|
||||
|
||||
```js
|
||||
assert.equal(document.querySelector('#search')?.getAttribute('tabindex'), '1');
|
||||
```
|
||||
|
||||
Your code should set the `tabindex` attribute on the `submit` `input` tag to a value of 2.
|
||||
|
||||
```js
|
||||
assert.equal(document.querySelector('#submit')?.getAttribute('tabindex'), '2');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<div tabindex="1" class="overlay"></div>
|
||||
<header>
|
||||
<h1>Even Deeper Thoughts with Master Camper Cat</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="">Home</a></li>
|
||||
<li><a href="">Blog</a></li>
|
||||
<li><a href="">Training</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<form>
|
||||
<label for="search">Search:</label>
|
||||
|
||||
|
||||
<input type="search" name="search" id="search">
|
||||
<input type="submit" name="submit" value="Submit" id="submit">
|
||||
|
||||
|
||||
</form>
|
||||
<h2>Inspirational Quotes</h2>
|
||||
<blockquote>
|
||||
<p>“There's no Theory of Evolution, just a list of creatures I've allowed to live.”<br>
|
||||
- Chuck Norris</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>“Wise men say forgiveness is divine, but never pay full price for late pizza.”<br>
|
||||
- TMNT</p>
|
||||
</blockquote>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
<style>
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 8px;
|
||||
}
|
||||
.overlay {
|
||||
margin: -8px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<body>
|
||||
<div tabindex="1" class="overlay"></div>
|
||||
<header>
|
||||
<h1>Even Deeper Thoughts with Master Camper Cat</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="">Home</a></li>
|
||||
<li><a href="">Blog</a></li>
|
||||
<li><a href="">Training</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<form>
|
||||
<label for="search">Search:</label>
|
||||
|
||||
|
||||
<input tabindex="1" type="search" name="search" id="search">
|
||||
<input tabindex="2" type="submit" name="submit" value="Submit" id="submit">
|
||||
|
||||
|
||||
</form>
|
||||
<h2>Inspirational Quotes</h2>
|
||||
<blockquote>
|
||||
<p>“There's no Theory of Evolution, just a list of creatures I've allowed to live.”<br>
|
||||
- Chuck Norris</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>“Wise men say forgiveness is divine, but never pay full price for late pizza.”<br>
|
||||
- TMNT</p>
|
||||
</blockquote>
|
||||
<footer>© 2018 Camper Cat</footer>
|
||||
</body>
|
||||
<style>
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 8px;
|
||||
}
|
||||
.overlay {
|
||||
margin: -8px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
---
|
||||
id: 587d7791367417b2b2512ab3
|
||||
title: Create Visual Balance Using the text-align Property
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/c3b4EAp'
|
||||
forumTopicId: 301053
|
||||
dashedName: create-visual-balance-using-the-text-align-property
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
This section of the curriculum focuses on Applied Visual Design. The first group of challenges build on the given card layout to show a number of core principles.
|
||||
|
||||
Text is often a large part of web content. CSS has several options for how to align it with the `text-align` property.
|
||||
|
||||
`text-align: justify;` spaces the text so that each line has equal width.
|
||||
|
||||
`text-align: center;` centers the text
|
||||
|
||||
`text-align: right;` right-aligns the text
|
||||
|
||||
And `text-align: left;` (the default) left-aligns the text.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Align the `h4` tag's text, which says "Google", to the center. Then justify the paragraph tag which contains information about how Google was founded.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should use the text-align property on the `h4` tag to set it to `center`.
|
||||
|
||||
```js
|
||||
const h4Element =document.querySelector('h4')
|
||||
const h4Style = window.getComputedStyle(h4Element);
|
||||
assert.equal(h4Style?.textAlign, 'center');
|
||||
```
|
||||
|
||||
Your code should use the text-align property on the `p` tag to set it to `justify`.
|
||||
|
||||
```js
|
||||
const pElement =document.querySelector('p')
|
||||
const pStyle = window.getComputedStyle(pElement);
|
||||
assert.equal(pStyle?.textAlign, 'justify');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
|
||||
}
|
||||
p {
|
||||
|
||||
}
|
||||
.links {
|
||||
margin-right: 20px;
|
||||
|
||||
}
|
||||
.fullCard {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were Ph.D. students at Stanford University.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
margin-right: 20px;
|
||||
|
||||
}
|
||||
.fullCard {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were Ph.D. students at Stanford University.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
---
|
||||
id: 587d7791367417b2b2512ab4
|
||||
title: Adjust the Width of an Element Using the width Property
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cvVLPtN'
|
||||
forumTopicId: 301039
|
||||
dashedName: adjust-the-width-of-an-element-using-the-width-property
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You can specify the width of an element using the `width` property in CSS. Values can be given in relative length units (such as `em`), absolute length units (such as `px`), or as a percentage of its containing parent element. Here's an example that changes the width of an image to 220px:
|
||||
|
||||
```css
|
||||
img {
|
||||
width: 220px;
|
||||
}
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
Add a `width` property to the entire card and set it to an absolute value of 245px. Use the `fullCard` class to select the element.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should change the `width` property of the card to 245 pixels by using the `fullCard` class selector.
|
||||
|
||||
```js
|
||||
const fullCard = code.match(/\.fullCard\s*{[\s\S]+?[^}]}/g);
|
||||
const fullCardElement = document.querySelector('.fullCard');
|
||||
const fullCardStyle = window.getComputedStyle(fullCardElement);
|
||||
assert.match(code,/\.fullCard\s*{[\s\S]+?[^}]}/g);
|
||||
|
||||
assert.match(fullCard?.[0],/width\s*:\s*245px\s*(;|})/gi);
|
||||
assert.equal(fullCardStyle?.maxWidth, 'none');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
margin-right: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
.fullCard {
|
||||
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were Ph.D. students at Stanford University.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
margin-right: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were Ph.D. students at Stanford University.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
---
|
||||
id: 587d7791367417b2b2512ab5
|
||||
title: Adjust the Height of an Element Using the height Property
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cEDaDTN'
|
||||
forumTopicId: 301034
|
||||
dashedName: adjust-the-height-of-an-element-using-the-height-property
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You can specify the height of an element using the `height` property in CSS, similar to the `width` property. Here's an example that changes the height of an image to 20px:
|
||||
|
||||
```css
|
||||
img {
|
||||
height: 20px;
|
||||
}
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
Add a `height` property to the `h4` tag and set it to 25px.
|
||||
|
||||
**Note:** You may need to be at 100% zoom to pass the test on this challenge.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should change the `h4` `height` property to a value of 25 pixels.
|
||||
|
||||
```js
|
||||
const spaceFreeText = document.querySelector("style:not(.fcc-hide-header)")?.textContent?.replace(/\s/g, '');
|
||||
const h4Element = document.querySelector('h4');
|
||||
assert.equal(Math.round(h4Element?.getBoundingClientRect()?.height),25);
|
||||
assert.match(spaceFreeText,/h4{\S*height:25px(;\S*}|})/);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
margin-right: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were Ph.D. students at Stanford University.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
margin-right: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were Ph.D. students at Stanford University.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
---
|
||||
id: 587d781a367417b2b2512ab7
|
||||
title: Use the strong Tag to Make Text Bold
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/ceJNBSb'
|
||||
forumTopicId: 301080
|
||||
dashedName: use-the-strong-tag-to-make-text-bold
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
To make text bold, you can use the `strong` tag. This is often used to draw attention to text and symbolize that it is important. With the `strong` tag, the browser applies the CSS of `font-weight: bold;` to the element.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Wrap a `strong` tag around the text `Stanford University` inside the `p` tag (do not include the period).
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add one `strong` tag to the markup.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('strong'),1);
|
||||
```
|
||||
|
||||
The `strong` tag should be inside the `p` tag.
|
||||
|
||||
```js
|
||||
const paragraphElement = document.querySelector('p');
|
||||
const paragraphChildren = paragraphElement?.querySelectorAll(`:scope ${'strong'}`);
|
||||
assert.lengthOf(paragraphChildren, 1);
|
||||
```
|
||||
|
||||
The `strong` tag should wrap around the words `Stanford University`.
|
||||
|
||||
```js
|
||||
assert.match(document.querySelector('strong')?.textContent, /^Stanford University\.?$/gi);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were Ph.D. students at Stanford University.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were Ph.D. students at <strong>Stanford University</strong>.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
---
|
||||
id: 587d781a367417b2b2512ab8
|
||||
title: Use the u Tag to Underline Text
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cN6aQCL'
|
||||
forumTopicId: 301082
|
||||
dashedName: use-the-u-tag-to-underline-text
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
To underline text, you can use the `u` tag. This is often used to signify that a section of text is important, or something to remember. With the `u` tag, the browser applies the CSS of `text-decoration: underline;` to the element.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Wrap the `u` tag only around the text `Ph.D. students`.
|
||||
|
||||
**Note:** Try to avoid using the `u` tag when it could be confused for a link. Anchor tags also have a default underlined formatting.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add a `u` tag to the markup.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('u'), 1);
|
||||
```
|
||||
|
||||
The `u` tag should wrap around the text `Ph.D. students`.
|
||||
|
||||
```js
|
||||
assert.equal(document.querySelector('u')?.textContent , 'Ph.D. students');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were Ph.D. students at <strong>Stanford University</strong>.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
---
|
||||
id: 587d781a367417b2b2512ab9
|
||||
title: Use the em Tag to Italicize Text
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cVJRBtp'
|
||||
forumTopicId: 301078
|
||||
dashedName: use-the-em-tag-to-italicize-text
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
To emphasize text, you can use the `em` tag. This displays text as italicized, as the browser applies the CSS of `font-style: italic;` to the element.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Wrap an `em` tag around the contents of the paragraph tag to give it emphasis.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add an `em` tag to the markup.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('em'),1);
|
||||
```
|
||||
|
||||
The `em` tag should wrap around the contents of the `p` tag but not the `p` tag itself.
|
||||
|
||||
```js
|
||||
const paragraphElement = document.querySelector('p');
|
||||
const emElement = document.querySelector('em');
|
||||
assert.lengthOf(paragraphElement?.children, 1);
|
||||
assert.lengthOf(emElement?.children, 2);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
---
|
||||
id: 587d781b367417b2b2512aba
|
||||
title: Use the s Tag to Strikethrough Text
|
||||
challengeType: 0
|
||||
forumTopicId: 301079
|
||||
dashedName: use-the-s-tag-to-strikethrough-text
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
To strikethrough text, which is when a horizontal line cuts across the characters, you can use the `s` tag. It shows that a section of text is no longer valid. With the `s` tag, the browser applies the CSS of `text-decoration: line-through;` to the element.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Wrap the `s` tag around `Google` inside the `h4` tag and then add the word `Alphabet` beside it without the strikethrough formatting.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add one `s` tag to the markup.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('s'),1);
|
||||
```
|
||||
|
||||
A `s` tag should wrap around the `Google` text in the `h4` tag. It should not contain the word `Alphabet`.
|
||||
|
||||
```js
|
||||
assert.match(document.querySelector('h4 > s')?.textContent, /Google/gi);
|
||||
assert.notMatch(document.querySelector('h4 > s')?.textContent, /Alphabet/gi);
|
||||
```
|
||||
|
||||
You should include the word `Alphabet` in the `h4` tag, without strikethrough formatting.
|
||||
|
||||
```js
|
||||
assert.match(document.querySelector('h4')?.innerHTML, /Alphabet/gi);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4><s>Google</s> Alphabet</h4>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
---
|
||||
id: 587d781b367417b2b2512abb
|
||||
title: Create a Horizontal Line Using the hr Element
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/c3bR8t7'
|
||||
forumTopicId: 301049
|
||||
dashedName: create-a-horizontal-line-using-the-hr-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You can use the `hr` tag to add a horizontal line across the width of its containing element. This can be used to define a change in topic or to visually separate groups of content.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Add an `hr` tag underneath the `h4` which contains the card title.
|
||||
|
||||
**Note:** In HTML, `hr` is a void element, and therefore doesn't need a separate closing tag.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add an `hr` tag to the markup.
|
||||
|
||||
```js
|
||||
assert.lengthOf(document.querySelectorAll('hr'),1);
|
||||
```
|
||||
|
||||
The `hr` tag should come between the title and the paragraph.
|
||||
|
||||
```js
|
||||
assert.match(code,/<\/h4>\s*?<hr(>|\s*?\/>)\s*?<p>/gi);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4><s>Google</s>Alphabet</h4>
|
||||
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4><s>Google</s>Alphabet</h4>
|
||||
<hr>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
---
|
||||
id: 587d781b367417b2b2512abc
|
||||
title: Adjust the background-color Property of Text
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cEDqwA6'
|
||||
forumTopicId: 301032
|
||||
dashedName: adjust-the-background-color-property-of-text
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Instead of adjusting your overall background or the color of the text to make the foreground easily readable, you can add a `background-color` to the element holding the text you want to emphasize. This challenge uses `rgba()` instead of `hex` codes or normal `rgb()`.
|
||||
|
||||
<blockquote>rgba stands for:<br> r = red<br> g = green<br> b = blue<br> a = alpha/level of opacity</blockquote>
|
||||
|
||||
The RGB values can range from 0 to 255. The alpha value can range from 1, which is fully opaque or a solid color, to 0, which is fully transparent or clear. `rgba()` is great to use in this case, as it allows you to adjust the opacity. This means you don't have to completely block out the background.
|
||||
|
||||
You'll use `background-color: rgba(45, 45, 45, 0.1)` for this challenge. It produces a dark gray color that is nearly transparent given the low opacity value of 0.1.
|
||||
|
||||
# --instructions--
|
||||
|
||||
To make the text stand out more, adjust the `background-color` of the `h4` element to the given `rgba()` value.
|
||||
|
||||
Also for the `h4`, remove the `height` property and add `padding` of 10px.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add a `background-color` property to the `h4` element set to `rgba(45, 45, 45, 0.1)`.
|
||||
|
||||
```js
|
||||
assert(
|
||||
/(background-color|background):rgba\(45,45,45,0?\.1\)(;?}|;)/gi.test(
|
||||
code.replace(/\s/g, '')
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
Your code should add a `padding` property to the `h4` element and set it to 10 pixels.
|
||||
|
||||
```js
|
||||
const h4Element = document.querySelector("h4");
|
||||
const h4style = window.getComputedStyle(h4Element);
|
||||
assert.equal(h4style?.paddingTop, "10px");
|
||||
assert.equal(h4style?.paddingRight, "10px");
|
||||
assert.equal(h4style?.paddingBottom, "10px");
|
||||
assert.equal(h4style?.paddingLeft, "10px");
|
||||
```
|
||||
|
||||
The `height` property on the `h4` element should be removed.
|
||||
|
||||
```js
|
||||
const h4Element = document.querySelector("h4");
|
||||
const h4style = window.getComputedStyle(h4Element);
|
||||
assert.notEqual(h4style?.height, '25px');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
|
||||
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Alphabet</h4>
|
||||
<hr>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
background-color: rgba(45, 45, 45, 0.1);
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Alphabet</h4>
|
||||
<hr>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
---
|
||||
id: 587d781b367417b2b2512abd
|
||||
title: Adjust the Size of a Heading Element Versus a Paragraph Element
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/c3bRPTz'
|
||||
forumTopicId: 301037
|
||||
dashedName: adjust-the-size-of-a-heading-element-versus-a-paragraph-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The font size of heading elements (`h1` through `h6`) should generally be larger than the font size of paragraph tags. This makes it easier for the user to visually understand the layout and level of importance of everything on the page. You use the `font-size` property to adjust the size of the text in an element.
|
||||
|
||||
# --instructions--
|
||||
|
||||
To make the heading significantly larger than the paragraph, change the `font-size` of the `h4` element to 27 pixels.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add a `font-size` property to the `h4` element set to 27 pixels.
|
||||
|
||||
```js
|
||||
|
||||
const h4Element =document.querySelector('h4')
|
||||
const h4Style = window.getComputedStyle(h4Element);
|
||||
assert.equal(h4Style?.fontSize,'27px');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
background-color: rgba(45, 45, 45, 0.1);
|
||||
padding: 10px;
|
||||
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Alphabet</h4>
|
||||
<hr>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
background-color: rgba(45, 45, 45, 0.1);
|
||||
padding: 10px;
|
||||
font-size: 27px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Alphabet</h4>
|
||||
<hr>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
---
|
||||
id: 587d781b367417b2b2512abe
|
||||
title: Add a box-shadow to a Card-like Element
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cvVZdUd'
|
||||
forumTopicId: 301031
|
||||
dashedName: add-a-box-shadow-to-a-card-like-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The `box-shadow` property applies one or more shadows to an element.
|
||||
|
||||
The `box-shadow` property takes the following values, in order:
|
||||
|
||||
<ul>
|
||||
<li><code>offset-x</code> (how far to push the shadow horizontally from the element)</li>
|
||||
<li><code>offset-y</code> (how far to push the shadow vertically from the element)</li>
|
||||
<li><code>blur-radius</code></li>
|
||||
<li><code>spread-radius</code></li>
|
||||
<li><code>color</code></li>
|
||||
</ul>
|
||||
|
||||
The `blur-radius` and `spread-radius` values are optional.
|
||||
|
||||
Multiple box-shadows can be created by using commas to separate properties of each `box-shadow` element.
|
||||
|
||||
Here's an example of the CSS to create multiple shadows with some blur, at mostly-transparent black colors:
|
||||
|
||||
```css
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
The element now has an id of `thumbnail`. With this selector, use the example CSS values above to place a `box-shadow` on the card.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should add a `box-shadow` property for the `thumbnail` id.
|
||||
|
||||
```js
|
||||
assert.match(code,(/#thumbnail\s*?{\s*?box-shadow/g));
|
||||
```
|
||||
|
||||
You should use the given CSS for the `box-shadow` value.
|
||||
|
||||
```js
|
||||
assert(
|
||||
code.match(
|
||||
/box-shadow:\s*?0\s+?10px\s+?20px\s+?rgba\(\s*?0\s*?,\s*?0\s*?,\s*?0\s*?,\s*?0?\.19\)\s*?,\s*?0\s+?6px\s+?6px\s+?rgba\(\s*?0\s*?,\s*?0\s*?,\s*?0\s*?,\s*?0?\.23\)/gi
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
background-color: rgba(45, 45, 45, 0.1);
|
||||
padding: 10px;
|
||||
font-size: 27px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard" id="thumbnail">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Alphabet</h4>
|
||||
<hr>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
background-color: rgba(45, 45, 45, 0.1);
|
||||
padding: 10px;
|
||||
font-size: 27px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
#thumbnail {
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard" id="thumbnail">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Alphabet</h4>
|
||||
<hr>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
---
|
||||
id: 587d781c367417b2b2512abf
|
||||
title: Decrease the Opacity of an Element
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/c7aKqu4'
|
||||
forumTopicId: 301055
|
||||
dashedName: decrease-the-opacity-of-an-element
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The `opacity` property in CSS is used to adjust the opacity, or conversely, the transparency for an item.
|
||||
|
||||
<blockquote>A value of 1 is opaque, which isn't transparent at all.<br>A value of 0.5 is half see-through.<br>A value of 0 is completely transparent.</blockquote>
|
||||
|
||||
The value given will apply to the entire element, whether that's an image with some transparency, or the foreground and background colors for a block of text.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Set the `opacity` of the anchor tags to 0.7 using `links` class to select them.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should set the `opacity` property to 0.7 on the anchor tags by selecting the class of `links`.
|
||||
|
||||
```js
|
||||
assert.match(
|
||||
document.querySelector("style:not(.fcc-hide-header)")?.textContent,
|
||||
/\.links\s*{([\s\S]*?;)*\s*opacity\s*:\s*0*\.70*\s*(;[\s\S]*?|\s*)}/
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
background-color: rgba(45, 45, 45, 0.1);
|
||||
padding: 10px;
|
||||
font-size: 27px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
|
||||
}
|
||||
#thumbnail {
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard" id="thumbnail">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Alphabet</h4>
|
||||
<hr>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
background-color: rgba(45, 45, 45, 0.1);
|
||||
padding: 10px;
|
||||
font-size: 27px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
opacity: 0.7;
|
||||
}
|
||||
#thumbnail {
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard" id="thumbnail">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Alphabet</h4>
|
||||
<hr>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
---
|
||||
id: 587d781c367417b2b2512ac0
|
||||
title: Use the text-transform Property to Make Text Uppercase
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cvVZQSP'
|
||||
forumTopicId: 301081
|
||||
dashedName: use-the-text-transform-property-to-make-text-uppercase
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The `text-transform` property in CSS is used to change the appearance of text. It's a convenient way to make sure text on a webpage appears consistently, without having to change the text content of the actual HTML elements.
|
||||
|
||||
The following table shows how the different `text-transform`values change the example text "Transform me".
|
||||
|
||||
<table><thead><tr><th>Value</th><th>Result</th></tr></thead><tbody><tr><td><code>lowercase</code></td><td>"transform me"</td></tr><tr><td><code>uppercase</code></td><td>"TRANSFORM ME"</td></tr><tr><td><code>capitalize</code></td><td>"Transform Me"</td></tr><tr><td><code>initial</code></td><td>Use the default value</td></tr><tr><td><code>inherit</code></td><td>Use the <code>text-transform</code> value from the parent element</td></tr><tr><td><code>none</code></td><td><strong>Default:</strong> Use the original text</td></tr></tbody></table>
|
||||
|
||||
# --instructions--
|
||||
|
||||
Transform the text of the `h4` to be uppercase using the `text-transform` property.
|
||||
|
||||
# --hints--
|
||||
|
||||
The `h4` text should be `uppercase`.
|
||||
|
||||
```js
|
||||
const h4Element =document.querySelector('h4')
|
||||
const h4Style = window.getComputedStyle(h4Element);
|
||||
assert.equal(h4Style?.textTransform, 'uppercase');
|
||||
```
|
||||
|
||||
The original text of the h4 should not be changed.
|
||||
|
||||
```js
|
||||
assert.notEqual(document.querySelector('h4')?.textContent, document.querySelector('h4')?.textContent?.toUpperCase());
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
background-color: rgba(45, 45, 45, 0.1);
|
||||
padding: 10px;
|
||||
font-size: 27px;
|
||||
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
opacity: 0.7;
|
||||
}
|
||||
#thumbnail {
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard" id="thumbnail">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Alphabet</h4>
|
||||
<hr>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
background-color: rgba(45, 45, 45, 0.1);
|
||||
padding: 10px;
|
||||
font-size: 27px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
text-align: left;
|
||||
color: black;
|
||||
opacity: 0.7;
|
||||
}
|
||||
#thumbnail {
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
.cardText {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard" id="thumbnail">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Alphabet</h4>
|
||||
<hr>
|
||||
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
---
|
||||
id: 587d781c367417b2b2512ac2
|
||||
title: Set the font-size for Multiple Heading Elements
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cPpQNT3'
|
||||
forumTopicId: 301067
|
||||
dashedName: set-the-font-size-for-multiple-heading-elements
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The `font-size` property is used to specify how large the text is in a given element. This rule can be used for multiple elements to create visual consistency of text on a page. In this challenge, you'll set the values for all `h1` through `h6` tags to balance the heading sizes.
|
||||
|
||||
# --instructions--
|
||||
|
||||
In the `style` tags, set the `font-size` of the:
|
||||
|
||||
- `h1` tag to 68px.
|
||||
- `h2` tag to 52px.
|
||||
- `h3` tag to 40px.
|
||||
- `h4` tag to 32px.
|
||||
- `h5` tag to 21px.
|
||||
- `h6` tag to 14px.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should set the `font-size` property for the `h1` tag to 68 pixels.
|
||||
|
||||
```js
|
||||
const fontSizeOfh1 = new __helpers.CSSHelp(document).getStyle('h1')?.getPropertyValue('font-size');
|
||||
assert.equal(fontSizeOfh1 ,'68px');
|
||||
```
|
||||
|
||||
Your code should set the `font-size` property for the `h2` tag to 52 pixels.
|
||||
|
||||
```js
|
||||
const fontSizeOfh2 = new __helpers.CSSHelp(document).getStyle('h2')?.getPropertyValue('font-size');
|
||||
assert.equal(fontSizeOfh2 ,'52px');
|
||||
```
|
||||
|
||||
Your code should set the `font-size` property for the `h3` tag to 40 pixels.
|
||||
|
||||
```js
|
||||
const fontSizeOfh3 = new __helpers.CSSHelp(document).getStyle('h3')?.getPropertyValue('font-size');
|
||||
assert.equal(fontSizeOfh3 ,'40px');
|
||||
```
|
||||
|
||||
Your code should set the `font-size` property for the `h4` tag to 32 pixels.
|
||||
|
||||
```js
|
||||
const fontSizeOfh4 = new __helpers.CSSHelp(document).getStyle('h4')?.getPropertyValue('font-size');
|
||||
assert.equal(fontSizeOfh4 , '32px');
|
||||
```
|
||||
|
||||
Your code should set the `font-size` property for the `h5` tag to 21 pixels.
|
||||
|
||||
```js
|
||||
const fontSizeOfh5 = new __helpers.CSSHelp(document).getStyle('h5')?.getPropertyValue('font-size');
|
||||
assert.equal(fontSizeOfh5 ,'21px');
|
||||
```
|
||||
|
||||
Your code should set the `font-size` property for the `h6` tag to 14 pixels.
|
||||
|
||||
```js
|
||||
const fontSizeOfh6 = new __helpers.CSSHelp(document).getStyle('h6')?.getPropertyValue('font-size');
|
||||
assert.equal(fontSizeOfh6 , '14px');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
<h1>This is h1 text</h1>
|
||||
<h2>This is h2 text</h2>
|
||||
<h3>This is h3 text</h3>
|
||||
<h4>This is h4 text</h4>
|
||||
<h5>This is h5 text</h5>
|
||||
<h6>This is h6 text</h6>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h1 {
|
||||
font-size: 68px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 52px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 40px;
|
||||
}
|
||||
h4 {
|
||||
font-size: 32px;
|
||||
}
|
||||
h5 {
|
||||
font-size: 21px;
|
||||
}
|
||||
h6 {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
<h1>This is h1 text</h1>
|
||||
<h2>This is h2 text</h2>
|
||||
<h3>This is h3 text</h3>
|
||||
<h4>This is h4 text</h4>
|
||||
<h5>This is h5 text</h5>
|
||||
<h6>This is h6 text</h6>
|
||||
```
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
---
|
||||
id: 587d781c367417b2b2512ac3
|
||||
title: Set the font-weight for Multiple Heading Elements
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/crVWRHq'
|
||||
forumTopicId: 301069
|
||||
dashedName: set-the-font-weight-for-multiple-heading-elements
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
You set the `font-size` of each heading tag in the last challenge, here you'll adjust the `font-weight`.
|
||||
|
||||
The `font-weight` property sets how thick or thin characters are in a section of text.
|
||||
|
||||
# --instructions--
|
||||
|
||||
<ul><li>Set the <code>font-weight</code> of the <code>h1</code> tag to 800.</li><li>Set the <code>font-weight</code> of the <code>h2</code> tag to 600.</li><li>Set the <code>font-weight</code> of the <code>h3</code> tag to 500.</li><li>Set the <code>font-weight</code> of the <code>h4</code> tag to 400.</li><li>Set the <code>font-weight</code> of the <code>h5</code> tag to 300.</li><li>Set the <code>font-weight</code> of the <code>h6</code> tag to 200.</li></ul>
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should set the `font-weight` property for the `h1` tag to 800.
|
||||
|
||||
```js
|
||||
const h1Element =document.querySelector('h1')
|
||||
const h1Style = window.getComputedStyle(h1Element);
|
||||
assert.equal(h1Style?.fontWeight, '800');
|
||||
```
|
||||
|
||||
Your code should set the `font-weight` property for the `h2` tag to 600.
|
||||
|
||||
```js
|
||||
const h2Element =document.querySelector('h2')
|
||||
const h2Style = window.getComputedStyle(h2Element);
|
||||
assert.equal(h2Style?.fontWeight, '600');
|
||||
```
|
||||
|
||||
Your code should set the `font-weight` property for the `h3` tag to 500.
|
||||
|
||||
```js
|
||||
const h3Element =document.querySelector('h3')
|
||||
const h3Style = window.getComputedStyle(h3Element);
|
||||
assert.equal(h3Style?.fontWeight, '500');
|
||||
```
|
||||
|
||||
Your code should set the `font-weight` property for the `h4` tag to 400.
|
||||
|
||||
```js
|
||||
const h4Element =document.querySelector('h4')
|
||||
const h4Style = window.getComputedStyle(h4Element);
|
||||
assert.equal(h4Style?.fontWeight, '400');
|
||||
```
|
||||
|
||||
Your code should set the `font-weight` property for the `h5` tag to 300.
|
||||
|
||||
```js
|
||||
const h5Element =document.querySelector('h5')
|
||||
const h5Style = window.getComputedStyle(h5Element);
|
||||
assert.equal(h5Style?.fontWeight, '300');
|
||||
```
|
||||
|
||||
Your code should set the `font-weight` property for the `h6` tag to 200.
|
||||
|
||||
```js
|
||||
const h6Element =document.querySelector('h6')
|
||||
const h6Style = window.getComputedStyle(h6Element);
|
||||
assert.equal(h6Style?.fontWeight, '200');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h1 {
|
||||
font-size: 68px;
|
||||
|
||||
}
|
||||
h2 {
|
||||
font-size: 52px;
|
||||
|
||||
}
|
||||
h3 {
|
||||
font-size: 40px;
|
||||
|
||||
}
|
||||
h4 {
|
||||
font-size: 32px;
|
||||
|
||||
}
|
||||
h5 {
|
||||
font-size: 21px;
|
||||
|
||||
}
|
||||
h6 {
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
</style>
|
||||
<h1>This is h1 text</h1>
|
||||
<h2>This is h2 text</h2>
|
||||
<h3>This is h3 text</h3>
|
||||
<h4>This is h4 text</h4>
|
||||
<h5>This is h5 text</h5>
|
||||
<h6>This is h6 text</h6>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h1 {
|
||||
font-size: 68px;
|
||||
font-weight: 800;
|
||||
}
|
||||
h2 {
|
||||
font-size: 52px;
|
||||
font-weight: 600;
|
||||
}
|
||||
h3 {
|
||||
font-size: 40px;
|
||||
font-weight: 500;
|
||||
}
|
||||
h4 {
|
||||
font-size: 32px;
|
||||
font-weight: 400;
|
||||
}
|
||||
h5 {
|
||||
font-size: 21px;
|
||||
font-weight: 300;
|
||||
}
|
||||
h6 {
|
||||
font-size: 14px;
|
||||
font-weight: 200;
|
||||
}
|
||||
</style>
|
||||
<h1>This is h1 text</h1>
|
||||
<h2>This is h2 text</h2>
|
||||
<h3>This is h3 text</h3>
|
||||
<h4>This is h4 text</h4>
|
||||
<h5>This is h5 text</h5>
|
||||
<h6>This is h6 text</h6>
|
||||
```
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
id: 587d781c367417b2b2512ac4
|
||||
title: Set the font-size of Paragraph Text
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cVJ36Cr'
|
||||
forumTopicId: 301068
|
||||
dashedName: set-the-font-size-of-paragraph-text
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The `font-size` property in CSS is not limited to headings, it can be applied to any element containing text.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Change the value of the `font-size` property for the paragraph to 16px to make it more visible.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `p` tag should have a `font-size` of 16 pixels.
|
||||
|
||||
```js
|
||||
const pElement =document.querySelector('p')
|
||||
const pStyle = window.getComputedStyle(pElement);
|
||||
assert.equal(pStyle?.fontSize, '16px');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
p {
|
||||
font-size: 10px;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
</p>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
</p>
|
||||
```
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
---
|
||||
id: 587d781d367417b2b2512ac5
|
||||
title: Set the line-height of Paragraphs
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/crVWdcv'
|
||||
forumTopicId: 301070
|
||||
dashedName: set-the-line-height-of-paragraphs
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
CSS offers the `line-height` property to change the height of each line in a block of text. As the name suggests, it changes the amount of vertical space that each line of text gets.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Add a `line-height` property to the `p` tag and set it to 25px.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your code should set the `line-height` of the `p` tag to 25 pixels.
|
||||
|
||||
```js
|
||||
const pElement =document.querySelector('p')
|
||||
const pStyle = window.getComputedStyle(pElement);
|
||||
assert.equal(pStyle?.lineHeight, '25px');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
p {
|
||||
font-size: 16px;
|
||||
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
</p>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 25px;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
</p>
|
||||
```
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
---
|
||||
id: 587d781d367417b2b2512ac8
|
||||
title: Adjust the Hover State of an Anchor Tag
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/cakRGcm'
|
||||
forumTopicId: 301035
|
||||
dashedName: adjust-the-hover-state-of-an-anchor-tag
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
This challenge will touch on the usage of pseudo-classes. A pseudo-class is a keyword that can be added to selectors, in order to select a specific state of the element.
|
||||
|
||||
For example, the styling of an anchor tag can be changed for its hover state using the `:hover` pseudo-class selector. Here's the CSS to change the `color` of the anchor tag to red during its hover state:
|
||||
|
||||
```css
|
||||
a:hover {
|
||||
color: red;
|
||||
}
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
The code editor has a CSS rule to style all `a` tags black. Add a rule so that when the user hovers over the `a` tag, the `color` is blue.
|
||||
|
||||
# --hints--
|
||||
|
||||
The anchor tag `color` should remain black, only add CSS rules for the `:hover` state.
|
||||
|
||||
```js
|
||||
const anchorElement = document.querySelector("a");
|
||||
const anchorStyle = window.getComputedStyle(anchorElement);
|
||||
assert.equal(anchorStyle?.color, 'rgb(0, 0, 0)');
|
||||
```
|
||||
|
||||
The anchor tag should have a `color` of blue on hover.
|
||||
|
||||
```js
|
||||
assert.match(code,
|
||||
/a:hover\s*?{\s*?color:\s*?(blue|rgba\(\s*?0\s*?,\s*?0\s*?,\s*?255\s*?,\s*?1\s*?\)|#00F|rgb\(\s*?0\s*?,\s*?0\s*?,\s*?255\s*?\))\s*?;\s*?}/gi
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
<a href="https://freecatphotoapp.com/" target="_blank">CatPhotoApp</a>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
a:hover {
|
||||
color: rgba(0,0,255,1);
|
||||
}
|
||||
</style>
|
||||
<a href="https://freecatphotoapp.com/" target="_blank">CatPhotoApp</a>
|
||||
```
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
---
|
||||
id: 587d781e367417b2b2512ac9
|
||||
title: Change an Element's Relative Position
|
||||
challengeType: 0
|
||||
videoUrl: 'https://scrimba.com/c/czVmMtZ'
|
||||
forumTopicId: 301044
|
||||
dashedName: change-an-elements-relative-position
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
CSS treats each HTML element as its own box, which is usually referred to as the <dfn>CSS Box Model</dfn>. Block-level items automatically start on a new line (think headings, paragraphs, and divs) while inline items sit within surrounding content (like images or spans). The default layout of elements in this way is called the <dfn>normal flow</dfn> of a document, but CSS offers the position property to override it.
|
||||
|
||||
When the position of an element is set to `relative`, it allows you to specify how CSS should move it *relative* to its current position in the normal flow of the page. It pairs with the CSS offset properties of `left` or `right`, and `top` or `bottom`. These say how many pixels, percentages, or ems to move the item *away* from where it is normally positioned. The following example moves the paragraph 10 pixels away from the bottom:
|
||||
|
||||
```css
|
||||
p {
|
||||
position: relative;
|
||||
bottom: 10px;
|
||||
}
|
||||
```
|
||||
|
||||
Changing an element's position to relative does not remove it from the normal flow - other elements around it still behave as if that item were in its default position.
|
||||
|
||||
**Note:** Positioning gives you a lot of flexibility and power over the visual layout of a page. It's good to remember that no matter the position of elements, the underlying HTML markup should be organized and make sense when read from top to bottom. This is how users with visual impairments (who rely on assistive devices like screen readers) access your content.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Change the `position` of the `h2` to `relative`, and use a CSS offset to move it 15 pixels away from the `top` of where it sits in the normal flow. Notice there is no impact on the positions of the surrounding h1 and p elements.
|
||||
|
||||
# --hints--
|
||||
|
||||
The `h2` element should have a `position` property set to `relative`.
|
||||
|
||||
```js
|
||||
const h2Element =document.querySelector('h2')
|
||||
const h2Style = window.getComputedStyle(h2Element);
|
||||
assert.equal(h2Style?.position, 'relative');
|
||||
```
|
||||
|
||||
Your code should use a CSS offset to relatively position the `h2` 15px away from the `top` of where it normally sits.
|
||||
|
||||
```js
|
||||
const h2Element =document.querySelector('h2')
|
||||
const h2Style = window.getComputedStyle(h2Element);
|
||||
assert.equal(h2Style?.top, '15px');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h2 {
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<h1>On Being Well-Positioned</h1>
|
||||
<h2>Move me!</h2>
|
||||
<p>I still think the h2 is where it normally sits.</p>
|
||||
</body>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<style>
|
||||
h2 {
|
||||
position: relative;
|
||||
top: 15px;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<h1>On Being Well-Positioned</h1>
|
||||
<h2>Move me!</h2>
|
||||
<p>I still think the h2 is where it normally sits.</p>
|
||||
</body>
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user